f8c587ddcf
* Prohibit files with name "." or ".." * Detect cycles caused by files with name "/" * Prohibit copy outside the target directory The commit also moves zip-related tests to PathRecursiveFunctionsZipTest.
605 lines
26 KiB
Kotlin
605 lines
26 KiB
Kotlin
/*
|
|
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
|
*/
|
|
|
|
package kotlin.jdk7.test
|
|
|
|
import test.testOnJvm8
|
|
import test.testOnJvm9AndAbove
|
|
import java.lang.NullPointerException
|
|
import java.nio.file.*
|
|
import java.util.zip.ZipEntry
|
|
import java.util.zip.ZipException
|
|
import java.util.zip.ZipOutputStream
|
|
import kotlin.io.path.*
|
|
import kotlin.jdk7.test.PathTreeWalkTest.Companion.createTestFiles
|
|
import kotlin.jdk7.test.PathTreeWalkTest.Companion.referenceFilenames
|
|
import kotlin.jdk7.test.PathTreeWalkTest.Companion.testVisitedFiles
|
|
import kotlin.test.*
|
|
|
|
class PathRecursiveFunctionsZipTest : AbstractPathTest() {
|
|
|
|
private fun Path.walkIncludeDirectories(): Sequence<Path> =
|
|
this.walk(PathWalkOption.INCLUDE_DIRECTORIES)
|
|
|
|
private fun createZipFile(parent: Path, name: String, entries: List<String>): Path {
|
|
val zipRoot = parent.resolve(name)
|
|
ZipOutputStream(zipRoot.outputStream()).use { out ->
|
|
for (fileName in entries) {
|
|
out.putNextEntry(ZipEntry(fileName))
|
|
if (!fileName.endsWith("/")) {
|
|
out.write(fileName.toByteArray())
|
|
}
|
|
out.closeEntry()
|
|
}
|
|
}
|
|
return zipRoot
|
|
}
|
|
|
|
private fun withZip(name: String, entries: List<String>, block: (parent: Path, zipRoot: Path) -> Unit) {
|
|
val parent = createTempDirectory().cleanupRecursively()
|
|
val archive = createZipFile(parent, name, entries)
|
|
val zipFs: FileSystem
|
|
try {
|
|
val classLoader: ClassLoader? = null
|
|
zipFs = FileSystems.newFileSystem(archive, classLoader)
|
|
} catch (e: ZipException) {
|
|
// Later JDK versions do not allow opening zip files that have entry named "." or "..".
|
|
// See https://bugs.openjdk.org/browse/JDK-8251329 and https://bugs.openjdk.org/browse/JDK-8283486
|
|
if (e.message?.contains("a '.' or '..' element") == true) {
|
|
println("Opening a ZIP file failed with $e")
|
|
} else {
|
|
// Potentially a different cause for the failed opening.
|
|
throw e
|
|
}
|
|
return
|
|
}
|
|
zipFs.use {
|
|
val zipRoot = it.getPath("/")
|
|
block(parent, zipRoot)
|
|
}
|
|
}
|
|
|
|
|
|
@Test
|
|
fun zipToDefaultPath() {
|
|
withZip("src.zip", listOf("directory/", "directory/file.txt")) { root, zipRoot ->
|
|
val dst = root.resolve("dst")
|
|
val src = zipRoot.resolve("directory")
|
|
|
|
src.copyToRecursively(dst, followLinks = false)
|
|
|
|
val expected = listOf("", "file.txt")
|
|
testVisitedFiles(expected, dst.walkIncludeDirectories(), dst)
|
|
assertEquals("directory/file.txt", dst.resolve("file.txt").readText())
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun defaultPathToZip() {
|
|
val srcRoot = createTestFiles().cleanupRecursively()
|
|
withZip("dst.zip", listOf("directory/", "directory/file.txt")) { _, zipRoot ->
|
|
val src = srcRoot.resolve("1").also { it.resolve("3/4.txt").writeText("hello") }
|
|
val dst = zipRoot.resolve("directory")
|
|
|
|
src.copyToRecursively(dst, followLinks = false)
|
|
|
|
val expected = listOf("", "2", "3", "3/4.txt", "3/5.txt", "file.txt")
|
|
testVisitedFiles(expected, dst.walkIncludeDirectories(), dst)
|
|
assertEquals("hello", zipRoot.resolve("directory/3/4.txt").readText())
|
|
}
|
|
}
|
|
|
|
private fun testWalkSucceeds(path: Path, vararg expectedContent: Set<Path>) {
|
|
val content = path.walkIncludeDirectories().toSet()
|
|
assertContains(expectedContent.toList(), content)
|
|
}
|
|
|
|
private fun testWalkFailsWithIllegalFileName(path: Path) {
|
|
assertFailsWith<IllegalFileNameException> {
|
|
path.walkIncludeDirectories().toList()
|
|
}
|
|
}
|
|
|
|
private inline fun <reified T> testWalkMaybeFailsWith(path: Path, vararg expectedContent: Set<Path>) {
|
|
try {
|
|
testWalkSucceeds(path, *expectedContent)
|
|
} catch (exception: Exception) {
|
|
assertIs<T>(exception)
|
|
}
|
|
}
|
|
|
|
private fun testCopySucceeds(source: Path, target: Path, vararg expectedTargetContent: Set<Path>) {
|
|
source.copyToRecursively(target, followLinks = false)
|
|
val content = target.walkIncludeDirectories().toSet()
|
|
assertContains(expectedTargetContent.toList(), content)
|
|
}
|
|
|
|
private fun testCopyFailsWithIllegalFileName(source: Path, target: Path) {
|
|
assertFailsWith<IllegalFileNameException> {
|
|
source.copyToRecursively(target, followLinks = false)
|
|
}
|
|
}
|
|
|
|
private inline fun <reified T> testCopyMaybeFailsWith(source: Path, target: Path, expectedTargetContent: Set<Path>) {
|
|
try {
|
|
testCopySucceeds(source, target, expectedTargetContent)
|
|
} catch (exception: Exception) {
|
|
assertIs<T>(exception)
|
|
}
|
|
}
|
|
|
|
private fun testDeleteSucceeds(path: Path) {
|
|
path.deleteRecursively()
|
|
assertFalse(path.exists())
|
|
}
|
|
|
|
private inline fun <reified T> testDeleteFailsWith(path: Path) {
|
|
assertFailsWith<FileSystemException> {
|
|
path.deleteRecursively()
|
|
}.also { exception ->
|
|
val suppressed = exception.suppressed.single()
|
|
assertIs<T>(suppressed)
|
|
}
|
|
}
|
|
|
|
private inline fun <reified T> testDeleteMaybeFailsWith(path: Path): Boolean {
|
|
return try {
|
|
testDeleteSucceeds(path)
|
|
true
|
|
} catch (exception: FileSystemException) {
|
|
val suppressed = exception.suppressed.single()
|
|
assertIs<T>(suppressed)
|
|
false
|
|
}
|
|
}
|
|
|
|
private fun Path.resolve(vararg entryNames: String): Set<Path> {
|
|
return entryNames.map { resolve(it) }.toSet()
|
|
}
|
|
|
|
@Test
|
|
fun zipDotFileName() {
|
|
withZip("Archive1.zip", listOf("normal", ".")) { root, zipRoot ->
|
|
val dotFile = zipRoot.resolve(".")
|
|
val dotDir = zipRoot.resolve("./")
|
|
testWalkFailsWithIllegalFileName(zipRoot)
|
|
testWalkFailsWithIllegalFileName(dotFile)
|
|
// Succeeds on jvm8, fails on jvm9+
|
|
testWalkMaybeFailsWith<IllegalFileNameException>(dotDir, setOf(dotDir))
|
|
|
|
val target = root.resolve("UnzipArchive1")
|
|
testCopyFailsWithIllegalFileName(zipRoot, target)
|
|
val dotFileTarget = root.resolve("UnzipArchive1-dotFile")
|
|
testCopyFailsWithIllegalFileName(dotFile, dotFileTarget)
|
|
val dotDirTarget = root.resolve("UnzipArchive1-dotDir")
|
|
// Succeeds on jvm8, fails on jvm9+
|
|
testCopyMaybeFailsWith<IllegalFileNameException>(dotDir, dotDirTarget, setOf(dotDirTarget))
|
|
|
|
testDeleteFailsWith<IllegalFileNameException>(zipRoot)
|
|
testDeleteFailsWith<IllegalFileNameException>(dotFile)
|
|
// Succeeds on jvm8, fails on jvm9+
|
|
testDeleteMaybeFailsWith<IllegalFileNameException>(dotDir)
|
|
assertTrue(dotFile.exists())
|
|
assertTrue(zipRoot.exists())
|
|
}
|
|
|
|
withZip("Archive2.zip", listOf("normal", "./")) { root, zipRoot ->
|
|
val dotFile = zipRoot.resolve(".")
|
|
val dotDir = zipRoot.resolve("./")
|
|
testWalkFailsWithIllegalFileName(zipRoot)
|
|
testWalkFailsWithIllegalFileName(dotFile)
|
|
// Succeeds on jvm8, fails on jvm9+
|
|
testWalkMaybeFailsWith<IllegalFileNameException>(dotDir, setOf(dotDir))
|
|
|
|
val target = root.resolve("UnzipArchive2")
|
|
testCopyFailsWithIllegalFileName(zipRoot, target)
|
|
val dotFileTarget = root.resolve("UnzipArchive2-dotFile")
|
|
testCopyFailsWithIllegalFileName(dotFile, dotFileTarget)
|
|
val dotDirTarget = root.resolve("UnzipArchive2-dotDir")
|
|
// Succeeds on jvm8, fails on jvm9+
|
|
testCopyMaybeFailsWith<IllegalFileNameException>(dotDir, dotDirTarget, setOf(dotDirTarget))
|
|
|
|
testDeleteFailsWith<IllegalFileNameException>(zipRoot)
|
|
testDeleteFailsWith<IllegalFileNameException>(dotFile)
|
|
// Succeeds on jvm8, fails on jvm9+
|
|
testDeleteMaybeFailsWith<IllegalFileNameException>(dotDir)
|
|
assertTrue(dotFile.exists())
|
|
assertTrue(zipRoot.exists())
|
|
}
|
|
|
|
withZip("Archive3.zip", listOf("a/", "a/.")) { root, zipRoot ->
|
|
val a = zipRoot.resolve("a")
|
|
testWalkFailsWithIllegalFileName(zipRoot)
|
|
testWalkFailsWithIllegalFileName(a)
|
|
|
|
val target = root.resolve("UnzipArchive3")
|
|
testCopyFailsWithIllegalFileName(zipRoot, target)
|
|
val aTarget = root.resolve("UnzipArchive3-a")
|
|
testCopyFailsWithIllegalFileName(a, aTarget)
|
|
|
|
testDeleteFailsWith<IllegalFileNameException>(zipRoot)
|
|
testDeleteFailsWith<IllegalFileNameException>(a)
|
|
}
|
|
|
|
withZip("Archive4.zip", listOf("a/", "a/./")) { root, zipRoot ->
|
|
val a = zipRoot.resolve("a")
|
|
testWalkFailsWithIllegalFileName(zipRoot)
|
|
testWalkFailsWithIllegalFileName(a)
|
|
|
|
val target = root.resolve("UnzipArchive4")
|
|
testCopyFailsWithIllegalFileName(zipRoot, target)
|
|
val aTarget = root.resolve("UnzipArchive4-a")
|
|
testCopyFailsWithIllegalFileName(a, aTarget)
|
|
|
|
testDeleteFailsWith<IllegalFileNameException>(zipRoot)
|
|
testDeleteFailsWith<IllegalFileNameException>(a)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun zipSlashFileName() {
|
|
withZip("Archive1.zip", listOf("normal", "/")) { root, zipRoot ->
|
|
// Fails in jvm8-10, succeeds in jvm11
|
|
testWalkMaybeFailsWith<FileSystemLoopException>(zipRoot, zipRoot.resolve("", "normal"))
|
|
|
|
val target = root.resolve("UnzipArchive1")
|
|
// Fails in jvm8-10, succeeds in jvm11
|
|
testCopyMaybeFailsWith<FileSystemLoopException>(zipRoot, target, target.resolve("", "normal"))
|
|
|
|
// Throws FileSystemLoopException in jvm8-10
|
|
// Path.deleteIfExists on the root directory of the archive throws NullPointerException in jvm9+
|
|
assertFails { zipRoot.deleteRecursively() }
|
|
}
|
|
|
|
withZip("Archive2.zip", listOf("normal", "//")) { root, zipRoot ->
|
|
// Fails in jvm8, succeeds in jvm9+
|
|
try {
|
|
zipRoot.walkIncludeDirectories().toList()
|
|
// ["/", "//", "normal"] in jvm9-10
|
|
// ["/", "/normal"] in jvm11
|
|
} catch (exception: Exception) {
|
|
assertIs<FileSystemLoopException>(exception)
|
|
}
|
|
|
|
val target = root.resolve("UnzipArchive2")
|
|
// Fails in jvm8, succeeds in jvm9+
|
|
testCopyMaybeFailsWith<FileSystemLoopException>(zipRoot, target, target.resolve("", "normal"))
|
|
|
|
// Throws FileSystemLoopException in jvm8
|
|
// Path.deleteIfExists on the root directory of the archive throws NullPointerException in jvm9+
|
|
assertFails { zipRoot.deleteRecursively() }
|
|
}
|
|
|
|
withZip("Archive3.zip", listOf("a/", "a//")) { root, zipRoot ->
|
|
val aFile = zipRoot.resolve("a")
|
|
val aDir = zipRoot.resolve("a/")
|
|
// Fails in jvm8, succeeds in jvm9+
|
|
try {
|
|
zipRoot.walkIncludeDirectories().toList()
|
|
// ["/", "/a", "/a/"] in jvm9-10
|
|
// ["/", "/a"] in jvm11
|
|
} catch (exception: Exception) {
|
|
assertIs<FileSystemLoopException>(exception)
|
|
}
|
|
testWalkMaybeFailsWith<FileSystemLoopException>(aFile, setOf(aFile))
|
|
testWalkMaybeFailsWith<FileSystemLoopException>(aDir, setOf(aFile))
|
|
|
|
// Fails in jvm8, succeeds in jvm9+
|
|
val zipRootTarget = root.resolve("UnzipArchive3")
|
|
testCopyMaybeFailsWith<FileSystemLoopException>(zipRoot, zipRootTarget, zipRootTarget.resolve("", "a"))
|
|
val aFileTarget = root.resolve("UnzipArchive3-aFile")
|
|
testCopyMaybeFailsWith<FileSystemLoopException>(aFile, aFileTarget, setOf(aFileTarget))
|
|
val aDirTarget = root.resolve("UnzipArchive3-aDir")
|
|
// Fails with jdk8 "IllegalFileNameException: Copying files to outside the specified target directory is prohibited."
|
|
testCopyMaybeFailsWith<IllegalFileNameException>(aDir, aDirTarget, setOf(aDirTarget))
|
|
|
|
// Throws FileSystemLoopException in jvm8
|
|
// Path.deleteIfExists on the root directory of the archive throws NullPointerException in jvm9+
|
|
assertFails { zipRoot.deleteRecursively() }
|
|
// Fails in jvm8, succeeds in jvm9+
|
|
testDeleteMaybeFailsWith<FileSystemLoopException>(aFile)
|
|
testDeleteMaybeFailsWith<FileSystemLoopException>(aDir)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun copyOutsideTargetIsProhibited() {
|
|
withZip("Archive.zip", listOf("a", "a//")) { root, zipRoot ->
|
|
val aDir = zipRoot.resolve("a/")
|
|
testWalkSucceeds(aDir, zipRoot.resolve("a/", "a"))
|
|
|
|
val aDirTarget = root.resolve("UnzipArchive-aDir")
|
|
// Fails with jdk8 "IllegalFileNameException: Copying files to outside the specified target directory is prohibited."
|
|
testCopyMaybeFailsWith<IllegalFileNameException>(aDir, aDirTarget, setOf(aDirTarget))
|
|
// No file is copied outside the target
|
|
testWalkSucceeds(root, root.resolve("", "Archive.zip", "UnzipArchive-aDir"))
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun deleteZipRootDirectory() {
|
|
withZip("Archive.zip", emptyList()) { _, zipRoot ->
|
|
// Deleting the root directory of a zip archive throws NullPointerException.
|
|
assertFailsWith<NullPointerException> {
|
|
zipRoot.deleteIfExists()
|
|
}
|
|
}
|
|
}
|
|
|
|
// KT-63103
|
|
@Test
|
|
fun zipDoubleDotsFileName() {
|
|
withZip("Archive1.zip", listOf("normal", "../sneaky")) { root, zipRoot ->
|
|
root.resolve("sneaky").createFile().also { it.writeText("outer sneaky") }
|
|
testWalkFailsWithIllegalFileName(zipRoot)
|
|
|
|
val target = root.resolve("UnzipArchive1")
|
|
testCopyFailsWithIllegalFileName(zipRoot, target)
|
|
|
|
testDeleteFailsWith<IllegalFileNameException>(zipRoot)
|
|
}
|
|
withZip("Archive2.zip", listOf("normal", "../normal")) { root, zipRoot ->
|
|
testWalkFailsWithIllegalFileName(zipRoot)
|
|
|
|
val target = root.resolve("UnzipArchive2")
|
|
testCopyFailsWithIllegalFileName(zipRoot, target)
|
|
|
|
testDeleteFailsWith<IllegalFileNameException>(zipRoot)
|
|
}
|
|
|
|
withZip("Archive3.zip", listOf("normal", "../")) { root, zipRoot ->
|
|
testWalkFailsWithIllegalFileName(zipRoot)
|
|
|
|
val target = root.resolve("UnzipArchive3")
|
|
testCopyFailsWithIllegalFileName(zipRoot, target)
|
|
|
|
testDeleteFailsWith<IllegalFileNameException>(zipRoot)
|
|
}
|
|
|
|
withZip("Archive4.zip", listOf("normal", "..")) { root, zipRoot ->
|
|
testWalkFailsWithIllegalFileName(zipRoot)
|
|
|
|
val target = root.resolve("UnzipArchive4")
|
|
testCopyFailsWithIllegalFileName(zipRoot, target)
|
|
|
|
testDeleteFailsWith<IllegalFileNameException>(zipRoot)
|
|
}
|
|
|
|
withZip("Archive5.zip", listOf("normal", "../..")) { root, zipRoot ->
|
|
testWalkFailsWithIllegalFileName(zipRoot)
|
|
|
|
val target = root.resolve("UnzipArchive5")
|
|
testCopyFailsWithIllegalFileName(zipRoot, target)
|
|
|
|
testDeleteFailsWith<IllegalFileNameException>(zipRoot)
|
|
}
|
|
|
|
withZip("Archive6.zip", listOf("normal", "../")) { root, zipRoot ->
|
|
val targetParent = root.resolve("UnzipArchive6Parent").createDirectory()
|
|
val targetSibling = targetParent.resolve("UnzipArchive6Sibling").createFile()
|
|
val target = targetParent.resolve("UnzipArchive6").createDirectory()
|
|
assertFailsWith<IllegalFileNameException> {
|
|
zipRoot.copyToRecursively(target, followLinks = false) { src, dst ->
|
|
if (dst != target) {
|
|
dst.deleteRecursively()
|
|
src.copyTo(dst)
|
|
}
|
|
CopyActionResult.CONTINUE
|
|
}
|
|
}
|
|
|
|
assertTrue(target.exists())
|
|
assertTrue(targetSibling.exists())
|
|
}
|
|
|
|
withZip("Archive7.zip", listOf("normal", "..a..b..")) { root, zipRoot ->
|
|
testWalkSucceeds(zipRoot, zipRoot.resolve("", "normal", "..a..b.."))
|
|
|
|
val target = root.resolve("UnzipArchive7")
|
|
val unix = target.resolve("", "normal", "..a..b..") // In Linux and macOS
|
|
val windows = target.resolve("", "normal", "..a..b") // In Windows
|
|
testCopySucceeds(zipRoot, target, unix, windows)
|
|
|
|
// Path.deleteIfExists on the root directory of the archive throws NullPointerException
|
|
testDeleteFailsWith<NullPointerException>(zipRoot)
|
|
assertEquals(emptyList(), zipRoot.listDirectoryEntries())
|
|
}
|
|
|
|
withZip("Archive8.zip", listOf("b", "a/", "a/../b")) { root, zipRoot ->
|
|
val a = zipRoot.resolve("a")
|
|
|
|
testWalkFailsWithIllegalFileName(a)
|
|
|
|
val target = root.resolve("UnzipArchive8")
|
|
testCopyFailsWithIllegalFileName(a, target)
|
|
|
|
testDeleteFailsWith<IllegalFileNameException>(a)
|
|
}
|
|
|
|
withZip("Archive9.zip", listOf("b/", "b/d", "a/", "a/../b/c")) { root, zipRoot ->
|
|
val b = zipRoot.resolve("a/../b")
|
|
// Traverses the "b" directory outside "a"
|
|
val jvm8 = zipRoot.resolve("a/../b", "b/d")
|
|
val jvm11 = zipRoot.resolve("a/../b", "a/../b/d")
|
|
testWalkSucceeds(b, jvm8, jvm11)
|
|
|
|
val target = root.resolve("UnzipArchive9")
|
|
// Copied content of the "b" directory that is outside "a"
|
|
testCopySucceeds(b, target, target.resolve("", "d"))
|
|
|
|
testDeleteSucceeds(b)
|
|
// The deleted "/a/../b" path actually deleted the "b" outside "a"
|
|
assertNull(zipRoot.listDirectoryEntries().find { it.name == "b" })
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun zipWindowsPathSeparators() {
|
|
// When creating a zip archive, entries are added with the exact given names.
|
|
testOnJvm8 {
|
|
// JDK8 converts backslashes to slashes when reading entries, but later can't find those entries
|
|
withZip("Archive1.zip", listOf("b\\", "b\\d", "a\\")) { root, zipRoot ->
|
|
assertFailsWith<NoSuchFileException> {
|
|
zipRoot.walkIncludeDirectories().toList()
|
|
}
|
|
|
|
// There is no directory with name "a", thus empty walk sequence
|
|
testWalkSucceeds(zipRoot.resolve("a"), emptySet())
|
|
|
|
assertFailsWith<NoSuchFileException> {
|
|
val target = root.resolve("UnzipArchive1")
|
|
zipRoot.copyToRecursively(target, followLinks = false)
|
|
}
|
|
|
|
assertFailsWith<FileSystemException> {
|
|
zipRoot.deleteRecursively()
|
|
}.also { exception ->
|
|
exception.suppressed.forEach {
|
|
assertIs<NoSuchFileException>(it)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
testOnJvm9AndAbove {
|
|
// JDK9+ treats backslashes as part of entry name
|
|
withZip("Archive1.zip", listOf("b\\", "b\\d", "a\\", "a\\..\\b\\c")) { root, zipRoot ->
|
|
val expectedWalk = listOf("", "b\\", "b\\d", "a\\", "a\\..\\b\\c").map { "/$it" }.toSet()
|
|
val walk = zipRoot.walkIncludeDirectories().map { it.toString() }.toSet()
|
|
assertEquals(expectedWalk, walk)
|
|
|
|
// There is no directory with name "a", thus empty walk sequence
|
|
testWalkSucceeds(zipRoot.resolve("a"), emptySet())
|
|
|
|
val target = root.resolve("UnzipArchive1")
|
|
// Fails in Windows
|
|
testCopyMaybeFailsWith<NoSuchFileException>(zipRoot, target, target.resolve("", "b\\", "b\\d", "a\\", "a\\..\\b\\c"))
|
|
|
|
// Deleting a zip root throws NPE
|
|
testDeleteFailsWith<NullPointerException>(zipRoot)
|
|
// All entries inside are deleted
|
|
testWalkSucceeds(zipRoot, setOf(zipRoot))
|
|
}
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun copyIllegalFileNameExceptionPassedToOnError() {
|
|
withZip("Archive1.zip", listOf("normal", "..")) { root, zipRoot ->
|
|
val target = root.resolve("UnzipArchive1")
|
|
var failed = false
|
|
zipRoot.copyToRecursively(target, followLinks = false, onError = { _, _, exception ->
|
|
failed = true
|
|
assertIs<IllegalFileNameException>(exception)
|
|
OnErrorResult.SKIP_SUBTREE
|
|
})
|
|
assertTrue(failed)
|
|
}
|
|
withZip("Archive2.zip", listOf("normal", "/")) { root, zipRoot ->
|
|
val target = root.resolve("UnzipArchive2")
|
|
// FileSystemLoopException in jvm8-10, no exception in jvm11
|
|
zipRoot.copyToRecursively(target, followLinks = false, onError = { _, _, exception ->
|
|
assertIs<FileSystemLoopException>(exception)
|
|
OnErrorResult.SKIP_SUBTREE
|
|
})
|
|
}
|
|
withZip("Archive3.zip", listOf("normal", ".")) { root, zipRoot ->
|
|
val target = root.resolve("UnzipArchive3")
|
|
var failed = false
|
|
zipRoot.copyToRecursively(target, followLinks = false, onError = { _, _, exception ->
|
|
failed = true
|
|
assertIs<IllegalFileNameException>(exception)
|
|
OnErrorResult.SKIP_SUBTREE
|
|
})
|
|
assertTrue(failed)
|
|
}
|
|
}
|
|
|
|
// To demonstrate how recursive functions behave when the traversal starting path ends with "." or ".."
|
|
// in the default file system, not inside a zip.
|
|
@Test
|
|
fun legalDirectorySymbols() {
|
|
createTestFiles().cleanupRecursively().let { root ->
|
|
val path = root.resolve("1/3/.")
|
|
|
|
testWalkSucceeds(path, path.resolve("", "4.txt", "5.txt"))
|
|
|
|
val target = createTempDirectory().cleanupRecursively().resolve("target")
|
|
testCopySucceeds(path, target, target.resolve("", "4.txt", "5.txt"))
|
|
|
|
// Fails in Linux and macOS, succeeds in Windows
|
|
// deleteIfExists/deleteDirectory() throws FileSystemException: /1/3/.: Invalid argument
|
|
val deleteSucceeded = testDeleteMaybeFailsWith<FileSystemException>(path)
|
|
if (deleteSucceeded) {
|
|
// Only the "1/3" directory and its content is deleted
|
|
val expectedRootContent = setOf(root) + referenceFilenames.filter { !it.contains("3") }.map { root.resolve(it) }
|
|
testWalkSucceeds(root, expectedRootContent)
|
|
}
|
|
}
|
|
|
|
createTestFiles().cleanupRecursively().let { root ->
|
|
val path = root.resolve("1/3/4.txt/.")
|
|
|
|
val unix = emptySet<Path>() // In Linux and macOS
|
|
val windows = setOf(path) // In Windows
|
|
testWalkSucceeds(path, unix, windows)
|
|
|
|
val target = createTempDirectory().cleanupRecursively().resolve("target")
|
|
// Copy fails in Linux and macOS, succeeds in Windows
|
|
// Path.copyToRecursively throws NoSuchFileException: /1/3/4.txt/.: The source file doesn't exist.
|
|
testCopyMaybeFailsWith<NoSuchFileException>(path, target, setOf(target))
|
|
|
|
// Delete fails in Linux and macOS, succeeds in Windows
|
|
// Path.deleteIfExists() throws FileSystemException: /1/3/4.txt/.: Not a directory
|
|
val deleteSucceeded = testDeleteMaybeFailsWith<FileSystemException>(path)
|
|
if (deleteSucceeded) {
|
|
// Only the "1/3/4.txt" file is deleted
|
|
val expectedRootContent = setOf(root) + referenceFilenames.filter { !it.contains("4.txt") }.map { root.resolve(it) }
|
|
testWalkSucceeds(root, expectedRootContent)
|
|
}
|
|
}
|
|
|
|
createTestFiles().cleanupRecursively().let { root ->
|
|
val path = root.resolve("1/3/..")
|
|
|
|
testWalkSucceeds(path, path.resolve("", "2", "3", "3/4.txt", "3/5.txt"))
|
|
|
|
val target = createTempDirectory().cleanupRecursively().resolve("target")
|
|
testCopySucceeds(path, target, target.resolve("", "2", "3", "3/4.txt", "3/5.txt"))
|
|
|
|
// Fails in macOS and Linux, succeeds in Windows
|
|
// In macOS Path.isSameFileAs() throws NoSuchFileException: /1/3/../2
|
|
// In Linux deleteDirectory() throws DirectoryNotEmptyException wrapped into FileSystemException: /1/3/..
|
|
val deleteSucceeded = testDeleteMaybeFailsWith<FileSystemException>(path)
|
|
if (deleteSucceeded) {
|
|
// Only the "1" directory and its content is deleted
|
|
val expectedRootContent = setOf(root) + referenceFilenames.filter { !it.contains("1") }.map { root.resolve(it) }
|
|
testWalkSucceeds(root, expectedRootContent)
|
|
}
|
|
}
|
|
|
|
createTestFiles().cleanupRecursively().let { root ->
|
|
val path = root.resolve("1/3/4.txt/..")
|
|
|
|
val unix = emptySet<Path>() // In Linux and macOS
|
|
val windows = path.resolve("", "4.txt", "5.txt") // In Windows
|
|
testWalkSucceeds(path, unix, windows)
|
|
|
|
val target = createTempDirectory().cleanupRecursively().resolve("target")
|
|
// Copy fails in Linux and macOS, succeeds in Windows
|
|
// Path.copyToRecursively throws NoSuchFileException: /1/3/4.txt/..: The source file doesn't exist.
|
|
testCopyMaybeFailsWith<NoSuchFileException>(path, target, setOf(target, target.resolve("4.txt"), target.resolve("5.txt")))
|
|
|
|
// Delete fails in Linux and macOS, succeeds in Windows
|
|
// Path.deleteIfExists() throws FileSystemException: /1/3/4.txt/..: Not a directory
|
|
val deleteSucceeded = testDeleteMaybeFailsWith<FileSystemException>(path)
|
|
if (deleteSucceeded) {
|
|
// Only the "1/3" directory and its content is deleted
|
|
val expectedRootContent = setOf(root) + referenceFilenames.filter { !it.contains("3") }.map { root.resolve(it) }
|
|
testWalkSucceeds(root, expectedRootContent)
|
|
}
|
|
}
|
|
}
|
|
} |