Skip to content

Commit

Permalink
devonfw#799: fix zip extraction to preserve file attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
hohwille committed Dec 3, 2024
1 parent e849467 commit 370e539
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 69 deletions.
2 changes: 1 addition & 1 deletion cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
115 changes: 47 additions & 68 deletions cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<String, String> FS_ENV = Map.of("encoding", "UTF-8");

private final IdeContext context;

/**
Expand Down Expand Up @@ -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<Path> 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
Expand All @@ -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);
}

/**
Expand Down Expand Up @@ -708,34 +708,6 @@ private void extractArchive(Path file, Path targetDir, Function<InputStream, Arc
}
}

private void extractZipArchive(Path file, Path targetDir) {

this.context.trace("Unpacking archive {} to {}", file, targetDir);
try (FileInputStream fis = new FileInputStream(file.toFile()); ZipInputStream zis = new ZipInputStream(fis); IdeProgressBar pb = getProgressbarForUnpacking(
getFileSize(file))) {
ZipEntry entry = zis.getNextEntry();
while (entry != null) {
Path entryName = Path.of(entry.getName());
Path entryPath = targetDir.resolve(entryName).toAbsolutePath();
if (!entryPath.startsWith(targetDir)) {
throw new IOException("Preventing path traversal attack from " + entryName + " to " + entryPath);
}
if (entry.isDirectory()) {
mkdirs(entryPath);
} else {
// ensure the file can also be created if directory entry was missing or out of order...
mkdirs(entryPath.getParent());
Files.copy(zis, entryPath);
}
pb.stepBy(entry.getCompressedSize());
zis.closeEntry();
entry = zis.getNextEntry();
}
} catch (IOException e) {
throw new IllegalStateException("Failed to extract " + file + " to " + targetDir, e);
}
}

@Override
public void extractDmg(Path file, Path targetDir) {

Expand Down Expand Up @@ -921,11 +893,12 @@ public Path findExistingFile(String fileName, List<Path> 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<PosixFilePermission> perms;
try {
Expand All @@ -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 {
Expand Down

0 comments on commit 370e539

Please sign in to comment.