Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/main/java/org/codehaus/plexus/util/FileUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -1247,7 +1247,9 @@ private static void deleteDirectoryOnExit(final File directory) throws IOExcepti
}
directory.deleteOnExit(); // The hook reverses the list

cleanDirectoryOnExit(directory);
if (!NioFiles.isSymbolicLink(directory)) {
cleanDirectoryOnExit(directory);
}
}

/**
Expand Down Expand Up @@ -1343,7 +1345,9 @@ public static void deleteDirectory(final File directory) throws IOException {
return;
}

cleanDirectory(directory);
if (!NioFiles.isSymbolicLink(directory)) {
cleanDirectory(directory);
}
if (!directory.delete()) {
final String message = "Directory " + directory + " unable to be deleted.";
throw new IOException(message);
Expand Down
63 changes: 63 additions & 0 deletions src/test/java/org/codehaus/plexus/util/FileUtilsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,16 @@
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Properties;
import java.util.concurrent.TimeUnit;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.OS;

import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assumptions.assumeFalse;

/**
* This is used to test FileUtils for correctness.
Expand Down Expand Up @@ -1185,6 +1188,66 @@ void deleteDirectoryWithDanglingSymlink() throws Exception {
assertFalse(getTestDirectory().exists(), "Failed to delete test directory");
}

@Test
@DisabledOnOs(OS.WINDOWS)
void deleteDirectoryDoesNotFollowDirSymlink() throws Exception {
File outside = new File(getTestDirectory(), "outside");
outside.mkdir();
File outsideFile = new File(outside, "keep.txt");
createFile(outsideFile, 1);

File directory = new File(getTestDirectory(), "directory");
File readOnly = new File(directory, "readOnly");
readOnly.mkdirs();
File symlink = new File(readOnly, "dirSymlink");
createSymlink(symlink, outside);
// the symlink cannot be unlinked from a read-only directory
readOnly.setWritable(false);
try {
assumeFalse(Files.isWritable(readOnly.toPath()), "directory permissions are not enforced");
assertThrows(IOException.class, () -> FileUtils.deleteDirectory(directory));
} finally {
readOnly.setWritable(true);
symlink.delete();
}
assertTrue(outsideFile.exists(), "deleteDirectory followed a symlink out of the directory");
}

@Test
@DisabledOnOs(OS.WINDOWS)
void forceDeleteOnExitDoesNotFollowDirSymlink() throws Exception {
File outside = new File(getTestDirectory(), "outside");
outside.mkdir();
File outsideFile = new File(outside, "keep.txt");
createFile(outsideFile, 1);

File directory = new File(getTestDirectory(), "directory");
directory.mkdir();
createSymlink(new File(directory, "dirSymlink"), outside);

// deleteOnExit only runs when the JVM exits, so run it in a separate one
String java = new File(System.getProperty("java.home"), "bin/java").getPath();
Process process = new ProcessBuilder(
java,
"-cp",
System.getProperty("java.class.path"),
ForceDeleteOnExit.class.getName(),
directory.getAbsolutePath())
.inheritIO()
.start();
assertTrue(process.waitFor(1, TimeUnit.MINUTES), "JVM did not exit");
assertEquals(0, process.exitValue());

assertFalse(directory.exists(), "Failed to delete directory on exit");
assertTrue(outsideFile.exists(), "forceDeleteOnExit followed a symlink out of the directory");
}

public static final class ForceDeleteOnExit {
public static void main(String[] args) throws IOException {
FileUtils.forceDeleteOnExit(new File(args[0]));
}
}

@Test
void testcopyDirectoryLayoutWithExcludesIncludes() throws Exception {
File destination = new File("target", "copyDirectoryStructureWithExcludesIncludes");
Expand Down