Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
3 changes: 3 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ sourceSets{
}
}
test{
java {
srcDirs("test")
}
kotlin{
srcDirs("test")
}
Expand Down
5 changes: 2 additions & 3 deletions app/src/processing/app/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -688,9 +688,7 @@ static private void packageListFromFolder(File dir, String sofar,
* Ignores (does not extract) any __MACOSX files from macOS archives.
*/
static public void unzip(File zipFile, File dest) throws IOException {
FileInputStream fis = new FileInputStream(zipFile);
CheckedInputStream checksum = new CheckedInputStream(fis, new Adler32());
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(checksum));
try (ZipInputStream zis = new ZipInputStream( new BufferedInputStream( new CheckedInputStream( new FileInputStream(zipFile), new Adler32())))) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
final String name = entry.getName();
Expand All @@ -710,6 +708,7 @@ static public void unzip(File zipFile, File dest) throws IOException {
}
}
}
}


static protected void unzipEntry(ZipInputStream zin, File f) throws IOException {
Expand Down
81 changes: 81 additions & 0 deletions app/test/processing/app/UtilTest.java

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably better to not have a test than a platform specific test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package processing.app;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Assumptions;
import java.io.File;
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.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class UtilTest {

@Test
public void unzipLeaksFileDescriptorsOnException() throws IOException {
// thi only runs on Linux where /proc/self/fd exists otherwise skip
Assumptions.assumeTrue(new File("/proc/self/fd").exists(),
"Skipping test: /proc/self/fd not available (not Linux)");
// create a temporary zip file here with one entry
File zipFile = File.createTempFile("leak-test", ".zip");
zipFile.deleteOnExit();
File destDir = File.createTempFile("dest", "");
destDir.delete(); // turn into a directory
destDir.mkdirs();
destDir.deleteOnExit();
// build a simple zip file
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
ZipEntry entry = new ZipEntry("test.txt");
zos.putNextEntry(entry);
zos.write("hello".getBytes());
zos.closeEntry();
}
// make the destination directory read‑only – this will cause extraction to fail
destDir.setReadOnly();
boolean exceptionThrown = false;
try {
Util.unzip(zipFile, destDir);
} catch (IOException e) {
exceptionThrown = true;
}
assertTrue(exceptionThrown, "Expected an exception because destDir is read‑only");

// check if the file is open by examining /proc/self/fd symlinks
boolean fileStillOpen = isFileOpen(zipFile);
assertFalse(fileStillOpen, "File " + zipFile + " is still open after exception – leak detected");

destDir.setWritable(true);
destDir.delete();
zipFile.delete();
}

/**
* Checks whether the given file is currently open by the current process.
* Works on Linux by reading the symlinks in /proc/self/fd.
*/
private boolean isFileOpen(File file) throws IOException {
Path fdDir = Paths.get("/proc/self/fd");
String targetPath = file.getCanonicalPath();

try (Stream<Path> fdPaths = Files.list(fdDir)) {
return fdPaths
.map(Path::toFile)
.map(File::toPath)
.map(path -> {
try {
return Files.readSymbolicLink(path);
} catch (IOException e) {
return null; // not a symlink or inaccessible
}
})
.filter(resolved -> resolved != null)
.anyMatch(resolved -> resolved.toString().equals(targetPath));
}
}
}