diff --git a/src/main/java/net/lingala/zip4j/tasks/AbstractExtractFileTask.java b/src/main/java/net/lingala/zip4j/tasks/AbstractExtractFileTask.java index 1be30277..7bf3103a 100644 --- a/src/main/java/net/lingala/zip4j/tasks/AbstractExtractFileTask.java +++ b/src/main/java/net/lingala/zip4j/tasks/AbstractExtractFileTask.java @@ -59,14 +59,16 @@ protected void extractFile(ZipInputStream zipInputStream, FileHeader fileHeader, throw new ZipException("Could not create directory: " + outputFile); } } - } else if (isSymbolicLink(fileHeader)) { + } else if (isSymbolicLink) { createSymLink(zipInputStream, fileHeader, outputFile, progressMonitor); } else { checkOutputDirectoryStructure(outputFile); unzipFile(zipInputStream, outputFile, progressMonitor, readBuff); } - UnzipUtil.applyFileAttributes(fileHeader, outputFile); + if (!isSymbolicLink) { + UnzipUtil.applyFileAttributes(fileHeader, outputFile); + } } private void assertCanonicalPathsAreSame(File outputFile, String outputPath, FileHeader fileHeader) @@ -128,6 +130,11 @@ private void createSymLink(ZipInputStream zipInputStream, FileHeader fileHeader, try { Path linkTarget = Paths.get(symLinkPath); + if (outputFile.exists()) { + if (!outputFile.delete()) { + throw new ZipException("Could not delete existing symlink " + outputFile); + } + } Files.createSymbolicLink(outputFile.toPath(), linkTarget); } catch (NoSuchMethodError error) { try (OutputStream outputStream = new FileOutputStream(outputFile)) { diff --git a/src/main/java/net/lingala/zip4j/util/FileUtils.java b/src/main/java/net/lingala/zip4j/util/FileUtils.java index edd8ad13..3d84a3ce 100644 --- a/src/main/java/net/lingala/zip4j/util/FileUtils.java +++ b/src/main/java/net/lingala/zip4j/util/FileUtils.java @@ -558,9 +558,17 @@ private static byte[] getPosixFileAttributes(Path file) { LinkOption.NOFOLLOW_LINKS); Set posixFilePermissions = posixFileAttributeView.readAttributes().permissions(); - fileAttributes[3] = setBitIfApplicable(Files.isRegularFile(file), fileAttributes[3], 7); - fileAttributes[3] = setBitIfApplicable(Files.isDirectory(file), fileAttributes[3], 6); - fileAttributes[3] = setBitIfApplicable(Files.isSymbolicLink(file), fileAttributes[3], 5); + boolean isSymlink = Files.isSymbolicLink(file); + if (isSymlink) { + // Mark as a regular file and not a directory if file is a symlink and even if the symlink points to a directory + fileAttributes[3] = BitUtils.setBit(fileAttributes[3], 7); + fileAttributes[3] = BitUtils.unsetBit(fileAttributes[3], 6); + } else { + fileAttributes[3] = setBitIfApplicable(Files.isRegularFile(file), fileAttributes[3], 7); + fileAttributes[3] = setBitIfApplicable(Files.isDirectory(file), fileAttributes[3], 6); + } + + fileAttributes[3] = setBitIfApplicable(isSymlink, fileAttributes[3], 5); fileAttributes[3] = setBitIfApplicable(posixFilePermissions.contains(OWNER_READ), fileAttributes[3], 0); fileAttributes[2] = setBitIfApplicable(posixFilePermissions.contains(OWNER_WRITE), fileAttributes[2], 7); fileAttributes[2] = setBitIfApplicable(posixFilePermissions.contains(OWNER_EXECUTE), fileAttributes[2], 6); diff --git a/src/test/java/net/lingala/zip4j/ExtractZipFileIT.java b/src/test/java/net/lingala/zip4j/ExtractZipFileIT.java index bb97031e..d37faf8f 100644 --- a/src/test/java/net/lingala/zip4j/ExtractZipFileIT.java +++ b/src/test/java/net/lingala/zip4j/ExtractZipFileIT.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; @@ -328,6 +329,22 @@ public void testExtractFileWithFileNameAndNewFileNameExtractsSymlinkWhenSymlinkE assertThat(outputFolder.listFiles()[0].getName()).isEqualTo(newFileName); } + @Test + public void testSymlinkToDirectoryMaintainsSymlink() throws IOException { + verifyZipFileByExtractingAllFiles(getTestArchiveFromResources("zipWithLinkToDirAndFolder.zip"), null, outputFolder, 5, false); + + Path aDirectory = Paths.get(outputFolder.getPath(), "a"); + Path symlinkToDir = Paths.get(outputFolder.getPath(), "b"); + Path aFile = Paths.get(outputFolder.getPath(), "c"); + Path symlinkToFile = Paths.get(outputFolder.getPath(), "d"); + assertThat(aDirectory).isDirectory(); + assertThat(Files.isSymbolicLink(symlinkToDir)).isTrue(); + assertThat(aFile).isRegularFile(); + assertThat(symlinkToFile).isSymbolicLink(); + assertThat(Files.readSymbolicLink(symlinkToDir)).isEqualTo(aDirectory.getFileName()); + assertThat(Files.readSymbolicLink(symlinkToFile)).isEqualTo(aFile.getFileName()); + } + @Test public void testExtractFilesThrowsExceptionForWrongPasswordForAes() throws IOException { ZipParameters zipParameters = createZipParameters(EncryptionMethod.AES, AesKeyStrength.KEY_STRENGTH_256); diff --git a/src/test/resources/test-archives/zipWithLinkToDirAndFolder.zip b/src/test/resources/test-archives/zipWithLinkToDirAndFolder.zip new file mode 100644 index 00000000..3d2efe8a Binary files /dev/null and b/src/test/resources/test-archives/zipWithLinkToDirAndFolder.zip differ