From aa867585ab10df840055bb2142608abafed34d16 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 19 Jul 2023 10:00:29 +0200 Subject: [PATCH 01/11] reset to snapshot version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0083ba02..0a797544 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.cryptomator cryptofs - 2.6.6 + 2.7.0-SNAPSHOT Cryptomator Crypto Filesystem This library provides the Java filesystem provider used by Cryptomator. https://github.com/cryptomator/cryptofs From 2ab5f1065c22ccd3bf6f42924b13da296a7b556f Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Fri, 28 Jul 2023 17:36:14 +0200 Subject: [PATCH 02/11] Reformatted code --- ...yptoFileSystemProviderIntegrationTest.java | 1680 ++++++++--------- 1 file changed, 836 insertions(+), 844 deletions(-) diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java index c9f0b4bd..36a9fc4d 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java @@ -1,844 +1,836 @@ -/******************************************************************************* - * Copyright (c) 2016 Sebastian Stenzel and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the accompanying LICENSE.txt. - * - * Contributors: - * Sebastian Stenzel - initial API and implementation - *******************************************************************************/ -package org.cryptomator.cryptofs; - -import com.google.common.io.MoreFiles; -import com.google.common.jimfs.Configuration; -import com.google.common.jimfs.Jimfs; -import org.cryptomator.cryptofs.ch.CleartextFileChannel; -import org.cryptomator.cryptolib.api.Masterkey; -import org.cryptomator.cryptolib.api.MasterkeyLoader; -import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; -import org.hamcrest.MatcherAssert; -import org.hamcrest.Matchers; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Assumptions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.condition.EnabledOnOs; -import org.junit.jupiter.api.condition.OS; -import org.junit.jupiter.api.io.TempDir; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; -import org.junit.jupiter.params.provider.ValueSource; -import org.mockito.Mockito; - -import java.io.IOException; -import java.net.URI; -import java.nio.ByteBuffer; -import java.nio.channels.ClosedChannelException; -import java.nio.channels.FileChannel; -import java.nio.channels.FileLock; -import java.nio.channels.NonReadableChannelException; -import java.nio.channels.NonWritableChannelException; -import java.nio.channels.OverlappingFileLockException; -import java.nio.channels.ReadableByteChannel; -import java.nio.channels.WritableByteChannel; -import java.nio.charset.StandardCharsets; -import java.nio.file.AccessDeniedException; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.LinkOption; -import java.nio.file.NoSuchFileException; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.nio.file.attribute.BasicFileAttributeView; -import java.nio.file.attribute.BasicFileAttributes; -import java.nio.file.attribute.DosFileAttributeView; -import java.util.Arrays; -import java.util.Comparator; -import java.util.EnumSet; -import java.util.Set; - -import static java.nio.file.Files.readAllBytes; -import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; -import static org.cryptomator.cryptofs.CryptoFileSystemProperties.cryptoFileSystemProperties; -import static org.hamcrest.Matchers.is; - - -public class CryptoFileSystemProviderIntegrationTest { - - @Nested - @TestInstance(TestInstance.Lifecycle.PER_CLASS) - public class WithLimitedPaths { - - private MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); - private CryptoFileSystem fs; - private Path shortFilePath; - private Path shortSymlinkPath; - private Path shortDirPath; - - @BeforeAll - public void setup(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFailedException { - Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); - CryptoFileSystemProperties properties = cryptoFileSystemProperties() // - .withFlags() // - .withMasterkeyFilename("masterkey.cryptomator") // - .withKeyLoader(keyLoader) // - .withMaxCleartextNameLength(50) - .build(); - CryptoFileSystemProvider.initialize(tmpDir, properties, URI.create("test:key")); - fs = CryptoFileSystemProvider.newFileSystem(tmpDir, properties); - } - - @BeforeEach - public void setupEach() throws IOException { - shortFilePath = fs.getPath("/short-enough.txt"); - shortDirPath = fs.getPath("/short-enough-dir"); - shortSymlinkPath = fs.getPath("/symlink.txt"); - Files.createFile(shortFilePath); - Files.createDirectory(shortDirPath); - Files.createSymbolicLink(shortSymlinkPath, shortFilePath); - } - - @AfterEach - public void tearDownEach() throws IOException { - Files.deleteIfExists(shortFilePath); - Files.deleteIfExists(shortDirPath); - Files.deleteIfExists(shortSymlinkPath); - } - - @DisplayName("expect create file to fail with FileNameTooLongException") - @Test - public void testCreateFileExceedingPathLengthLimit() { - Path p = fs.getPath("/this-cleartext-filename-is-longer-than-50-characters"); - Assertions.assertThrows(FileNameTooLongException.class, () -> { - Files.createFile(p); - }); - } - - @DisplayName("expect create directory to fail with FileNameTooLongException") - @Test - public void testCreateDirExceedingPathLengthLimit() { - Path p = fs.getPath("/this-cleartext-filename-is-longer-than-50-characters"); - Assertions.assertThrows(FileNameTooLongException.class, () -> { - Files.createDirectory(p); - }); - } - - @DisplayName("expect create symlink to fail with FileNameTooLongException") - @Test - public void testCreateSymlinkExceedingPathLengthLimit() { - Path p = fs.getPath("/this-cleartext-filename-is-longer-than-50-characters"); - Assertions.assertThrows(FileNameTooLongException.class, () -> { - Files.createSymbolicLink(p, shortFilePath); - }); - } - - @DisplayName("expect move to fail with FileNameTooLongException") - @ParameterizedTest(name = "move {0} -> this-cleartext-filename-is-longer-than-50-characters") - @ValueSource(strings = {"/short-enough.txt", "/short-enough-dir", "/symlink.txt"}) - public void testMoveExceedingPathLengthLimit(String path) { - Path src = fs.getPath(path); - Path dst = fs.getPath("/this-cleartext-filename-is-longer-than-50-characters"); - Assertions.assertThrows(FileNameTooLongException.class, () -> { - Files.move(src, dst); - }); - Assertions.assertTrue(Files.exists(src)); - Assertions.assertTrue(Files.notExists(dst)); - } - - @DisplayName("expect copy to fail with FileNameTooLongException") - @ParameterizedTest(name = "copy {0} -> this-cleartext-filename-is-longer-than-50-characters") - @ValueSource(strings = {"/short-enough.txt", "/short-enough-dir", "/symlink.txt"}) - public void testCopyExceedingPathLengthLimit(String path) { - Path src = fs.getPath(path); - Path dst = fs.getPath("/this-cleartext-filename-is-longer-than-50-characters"); - Assertions.assertThrows(FileNameTooLongException.class, () -> { - Files.copy(src, dst, LinkOption.NOFOLLOW_LINKS); - }); - Assertions.assertTrue(Files.exists(src)); - Assertions.assertTrue(Files.notExists(dst)); - } - - } - - @Nested - @TestInstance(TestInstance.Lifecycle.PER_CLASS) - @TestMethodOrder(MethodOrderer.OrderAnnotation.class) - public class InMemoryOrdered { - - private FileSystem tmpFs; - private MasterkeyLoader keyLoader1; - private MasterkeyLoader keyLoader2; - private Path pathToVault1; - private Path pathToVault2; - private Path vaultConfigFile1; - private Path vaultConfigFile2; - private FileSystem fs1; - private FileSystem fs2; - - @BeforeAll - public void setup() throws IOException, MasterkeyLoadingFailedException { - tmpFs = Jimfs.newFileSystem(Configuration.unix()); - byte[] key1 = new byte[64]; - byte[] key2 = new byte[64]; - Arrays.fill(key1, (byte) 0x55); - Arrays.fill(key2, (byte) 0x77); - keyLoader1 = Mockito.mock(MasterkeyLoader.class); - keyLoader2 = Mockito.mock(MasterkeyLoader.class); - Mockito.when(keyLoader1.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(key1)); - Mockito.when(keyLoader2.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(key2)); - pathToVault1 = tmpFs.getPath("/vaultDir1"); - pathToVault2 = tmpFs.getPath("/vaultDir2"); - Files.createDirectory(pathToVault1); - Files.createDirectory(pathToVault2); - vaultConfigFile1 = pathToVault1.resolve("vault.cryptomator"); - vaultConfigFile2 = pathToVault2.resolve("vault.cryptomator"); - } - - @AfterAll - public void teardown() throws IOException { - tmpFs.close(); - } - - @Test - @Order(1) - @DisplayName("initialize vaults") - public void initializeVaults() { - Assertions.assertAll( - () -> { - var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader1).build(); - CryptoFileSystemProvider.initialize(pathToVault1, properties, URI.create("test:key")); - Assertions.assertTrue(Files.isDirectory(pathToVault1.resolve("d"))); - Assertions.assertTrue(Files.isRegularFile(vaultConfigFile1)); - }, () -> { - var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader2).build(); - CryptoFileSystemProvider.initialize(pathToVault2, properties, URI.create("test:key")); - Assertions.assertTrue(Files.isDirectory(pathToVault2.resolve("d"))); - Assertions.assertTrue(Files.isRegularFile(vaultConfigFile2)); - }); - } - - @Test - @Order(2) - @DisplayName("get filesystem with incorrect credentials") - public void testGetFsWithWrongCredentials() throws IOException { - Assumptions.assumeTrue(CryptoFileSystemProvider.checkDirStructureForVault(pathToVault1, "vault.cryptomator", "masterkey.cryptomator") == DirStructure.VAULT); - Assumptions.assumeTrue(CryptoFileSystemProvider.checkDirStructureForVault(pathToVault2, "vault.cryptomator", "masterkey.cryptomator") == DirStructure.VAULT); - Assertions.assertAll( - () -> { - URI fsUri = CryptoFileSystemUri.create(pathToVault1); - CryptoFileSystemProperties properties = cryptoFileSystemProperties() // - .withFlags() // - .withMasterkeyFilename("masterkey.cryptomator") // - .withKeyLoader(keyLoader2) // - .build(); - Assertions.assertThrows(VaultKeyInvalidException.class, () -> { - FileSystems.newFileSystem(fsUri, properties); - }); - }, - () -> { - URI fsUri = CryptoFileSystemUri.create(pathToVault2); - CryptoFileSystemProperties properties = cryptoFileSystemProperties() // - .withFlags() // - .withMasterkeyFilename("masterkey.cryptomator") // - .withKeyLoader(keyLoader1) // - .build(); - Assertions.assertThrows(VaultKeyInvalidException.class, () -> { - FileSystems.newFileSystem(fsUri, properties); - }); - }); - } - - @Test - @Order(4) - @DisplayName("get filesystem with correct credentials") - public void testGetFsViaNioApi() { - Assumptions.assumeTrue(Files.exists(vaultConfigFile1)); - Assumptions.assumeTrue(Files.exists(vaultConfigFile2)); - Assertions.assertAll( - () -> { - URI fsUri = CryptoFileSystemUri.create(pathToVault1); - fs1 = FileSystems.newFileSystem(fsUri, cryptoFileSystemProperties().withKeyLoader(keyLoader1).build()); - Assertions.assertTrue(fs1 instanceof CryptoFileSystemImpl); - - FileSystem sameFs = FileSystems.getFileSystem(fsUri); - Assertions.assertSame(fs1, sameFs); - }, - () -> { - URI fsUri = CryptoFileSystemUri.create(pathToVault2); - fs2 = FileSystems.newFileSystem(fsUri, cryptoFileSystemProperties().withKeyLoader(keyLoader2).build()); - Assertions.assertTrue(fs2 instanceof CryptoFileSystemImpl); - - FileSystem sameFs = FileSystems.getFileSystem(fsUri); - Assertions.assertSame(fs2, sameFs); - }); - } - - @Test - @Order(5) - @DisplayName("touch /foo") - public void testOpenAndCloseFileChannel() throws IOException { - Assumptions.assumeTrue(fs1.isOpen()); - - try (FileChannel ch = FileChannel.open(fs1.getPath("/foo"), EnumSet.of(StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW))) { - Assertions.assertTrue(ch instanceof CleartextFileChannel); - } - } - - @Test - @Order(6) - @DisplayName("ln -s foo /link") - public void testCreateSymlink() { - Path target = fs1.getPath("/foo"); - Assumptions.assumeTrue(Files.isRegularFile(target)); - Path link = fs1.getPath("/link"); - - Assertions.assertDoesNotThrow(() -> { - Files.createSymbolicLink(link, target); - }); - } - - @Test - @Order(7) - @DisplayName("echo 'hello world' > /link") - public void testWriteToSymlink() throws IOException { - Path link = fs1.getPath("/link"); - Assumptions.assumeTrue(Files.isSymbolicLink(link)); - - Assertions.assertDoesNotThrow(() -> { - try (WritableByteChannel ch = Files.newByteChannel(link, StandardOpenOption.WRITE)) { - ch.write(StandardCharsets.US_ASCII.encode("hello world")); - } - }); - } - - @Test - @Order(7) - @DisplayName("cat `readlink -f /link`") - public void testReadFromSymlink() throws IOException { - Path link = fs1.getPath("/link"); - Assumptions.assumeTrue(Files.isSymbolicLink(link)); - Path target = Files.readSymbolicLink(link); - - try (ReadableByteChannel ch = Files.newByteChannel(target, StandardOpenOption.READ)) { - ByteBuffer buf = ByteBuffer.allocate(100); - ch.read(buf); - buf.flip(); - String str = StandardCharsets.US_ASCII.decode(buf).toString(); - Assertions.assertEquals("hello world", str); - } - } - - @Test - @Order(7) - @DisplayName("cp /link /otherlink") - public void testCopySymlinkSymlink() throws IOException { - Path src = fs1.getPath("/link"); - Path dst = fs1.getPath("/otherlink"); - Assumptions.assumeTrue(Files.isSymbolicLink(src)); - Assumptions.assumeTrue(Files.notExists(dst)); - Files.copy(src, dst, LinkOption.NOFOLLOW_LINKS); - Assertions.assertTrue(Files.isSymbolicLink(src)); - Assertions.assertTrue(Files.isSymbolicLink(dst)); - } - - @Test - @Order(8) - @DisplayName("rm /link") - public void testRemoveSymlink() throws IOException { - Path link = fs1.getPath("/link"); - Assumptions.assumeTrue(Files.isSymbolicLink(link)); - - Assertions.assertDoesNotThrow(() -> { - Files.delete(link); - }); - } - - @Test - @Order(8) - @DisplayName("rm /otherlink") - public void testRemoveOtherSymlink() throws IOException { - Path link = fs1.getPath("/otherlink"); - Assumptions.assumeTrue(Files.isSymbolicLink(link)); - - Assertions.assertDoesNotThrow(() -> { - Files.delete(link); - }); - } - - @Test - @Order(9) - @DisplayName("ln -s foo '/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet'") - public void testCreateSymlinkWithLongName() throws IOException { - Path target = fs1.getPath("/foo"); - Assumptions.assumeTrue(Files.isRegularFile(target)); - Path longNameLink = fs1.getPath("/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet"); - Files.createSymbolicLink(longNameLink, target); - MatcherAssert.assertThat(MoreFiles.listFiles(fs1.getPath("/")), Matchers.hasItem(longNameLink)); - Assertions.assertTrue(Files.exists(longNameLink)); - } - - @Test - @Order(10) - @DisplayName("mv '/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet' '/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat") - public void testMoveSymlinkWithLongNameToAnotherLongName() throws IOException { - Path longNameSource = fs1.getPath("/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet"); - Assumptions.assumeTrue(Files.isSymbolicLink(longNameSource)); - Path longNameTarget = longNameSource.resolveSibling("/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat"); - Files.move(longNameSource, longNameTarget); - Assertions.assertTrue(Files.exists(longNameTarget)); - Assertions.assertTrue(Files.notExists(longNameSource)); - } - - @Test - @Order(11) - @DisplayName("rm -r '/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat'") - public void testRemoveSymlinkWithLongName() throws IOException { - Path longNamePath = fs1.getPath("/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat"); - Files.delete(longNamePath); - Assertions.assertTrue(Files.notExists(longNamePath)); - } - @Test - @Order(12) - @DisplayName("mkdir '/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet'") - public void testCreateDirWithLongName() throws IOException { - Path longNamePath = fs1.getPath("/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet"); - Files.createDirectory(longNamePath); - Assertions.assertTrue(Files.isDirectory(longNamePath)); - MatcherAssert.assertThat(MoreFiles.listFiles(fs1.getPath("/")), Matchers.hasItem(longNamePath)); - } - - @Test - @Order(13) - @DisplayName("mv '/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet' '/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat") - public void testMoveDirWithLongNameToAnotherLongName() throws IOException { - Path longNameSource = fs1.getPath("/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet"); - Path longNameTarget = longNameSource.resolveSibling("/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat"); - Files.move(longNameSource, longNameTarget); - Assertions.assertTrue(Files.exists(longNameTarget)); - Assertions.assertTrue(Files.notExists(longNameSource)); - } - - @Test - @Order(14) - @DisplayName("rm -r '/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat'") - public void testRemoveDirWithLongName() throws IOException { - Path longNamePath = fs1.getPath("/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat"); - Files.delete(longNamePath); - Assertions.assertTrue(Files.notExists(longNamePath)); - } - - @Test - @Order(15) - @DisplayName("touch '/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet'") - public void testCreateFileWithLongName() throws IOException { - Path longNamePath = fs1.getPath("/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet"); - Files.createFile(longNamePath); - Assertions.assertTrue(Files.isRegularFile(longNamePath)); - MatcherAssert.assertThat(MoreFiles.listFiles(fs1.getPath("/")), Matchers.hasItem(longNamePath)); - } - - @Test - @Order(16) - @DisplayName("mv '/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet' '/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat") - public void testMoveFileWithLongNameToAnotherLongName() throws IOException { - Path longNameSource = fs1.getPath("/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet"); - Path longNameTarget = longNameSource.resolveSibling("/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat"); - Files.move(longNameSource, longNameTarget); - Assertions.assertTrue(Files.exists(longNameTarget)); - Assertions.assertTrue(Files.notExists(longNameSource)); - } - - @Test - @Order(17) - @DisplayName("rm -r '/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat'") - public void testRemoveFileWithLongName() throws IOException { - Path longNamePath = fs1.getPath("/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat"); - Files.delete(longNamePath); - Assertions.assertTrue(Files.notExists(longNamePath)); - } - - @Test - @Order(18) - @DisplayName("cp fs1:/foo fs2:/bar") - public void testCopyFileAcrossFilesystem() throws IOException { - Path file1 = fs1.getPath("/foo"); - Path file2 = fs2.getPath("/bar"); - Assumptions.assumeTrue(Files.isRegularFile(file1)); - Assumptions.assumeTrue(Files.notExists(file2)); - - Files.copy(file1, file2); - - Assertions.assertArrayEquals(readAllBytes(file1), readAllBytes(file2)); - } - - @Test - @Order(19) - @DisplayName("echo 'goodbye world' > /foo") - public void testWriteToFile() throws IOException { - Path file1 = fs1.getPath("/foo"); - Assumptions.assumeTrue(Files.isRegularFile(file1)); - - Assertions.assertDoesNotThrow(() -> { - Files.write(file1, "goodbye world".getBytes()); - }); - } - - @Test - @Order(20) - @DisplayName("cp -f fs1:/foo fs2:/bar") - public void testCopyFileAcrossFilesystemReplaceExisting() throws IOException { - Path file1 = fs1.getPath("/foo"); - Path file2 = fs2.getPath("/bar"); - Assumptions.assumeTrue(Files.isRegularFile(file1)); - Assumptions.assumeTrue(Files.isRegularFile(file2)); - - Files.copy(file1, file2, REPLACE_EXISTING); - - Assertions.assertArrayEquals(readAllBytes(file1), readAllBytes(file2)); - } - - @Test - @Order(21) - @DisplayName("readattr /attributes.txt") - public void testLazinessOfFileAttributeViews() throws IOException { - Path file = fs1.getPath("/attributes.txt"); - Assumptions.assumeTrue(Files.notExists(file)); - - BasicFileAttributeView attrView = Files.getFileAttributeView(file, BasicFileAttributeView.class); - Assertions.assertNotNull(attrView); - Assertions.assertThrows(NoSuchFileException.class, () -> { - attrView.readAttributes(); - }); - - Files.write(file, new byte[3], StandardOpenOption.CREATE_NEW); - BasicFileAttributes attrs = attrView.readAttributes(); - Assertions.assertNotNull(attrs); - Assertions.assertEquals(3, attrs.size()); - - Files.delete(file); - Assertions.assertThrows(NoSuchFileException.class, () -> { - attrView.readAttributes(); - }); - Assertions.assertEquals(3, attrs.size()); // attrs should be immutable once they are read. - } - - @Test - @Order(22) - @DisplayName("ln -s /linked/targetY /links/linkX") - public void testSymbolicLinks() throws IOException { - Path linksDir = fs1.getPath("/links"); - Assumptions.assumeTrue(Files.notExists(linksDir)); - Files.createDirectories(linksDir); - - Assertions.assertAll( - () -> { - Path link = linksDir.resolve("link1"); - Files.createDirectories(link.getParent()); - Files.createSymbolicLink(link, fs1.getPath("/linked/target1")); - Path target = Files.readSymbolicLink(link); - MatcherAssert.assertThat(target.getFileSystem(), is(link.getFileSystem())); // as per contract of readSymbolicLink - MatcherAssert.assertThat(target.toString(), Matchers.equalTo("/linked/target1")); - MatcherAssert.assertThat(link.resolveSibling(target).toString(), Matchers.equalTo("/linked/target1")); - }, - () -> { - Path link = linksDir.resolve("link2"); - Files.createDirectories(link.getParent()); - Files.createSymbolicLink(link, fs1.getPath("./target2")); - Path target = Files.readSymbolicLink(link); - MatcherAssert.assertThat(target.getFileSystem(), is(link.getFileSystem())); - MatcherAssert.assertThat(target.toString(), Matchers.equalTo("./target2")); - MatcherAssert.assertThat(link.resolveSibling(target).normalize().toString(), Matchers.equalTo("/links/target2")); - }, - () -> { - Path link = linksDir.resolve("link3"); - Files.createDirectories(link.getParent()); - Files.createSymbolicLink(link, fs1.getPath("../target3")); - Path target = Files.readSymbolicLink(link); - MatcherAssert.assertThat(target.getFileSystem(), is(link.getFileSystem())); - MatcherAssert.assertThat(target.toString(), Matchers.equalTo("../target3")); - MatcherAssert.assertThat(link.resolveSibling(target).normalize().toString(), Matchers.equalTo("/target3")); - } - ); - } - - @Test - @Order(22) - @DisplayName("mv -f fs1:/foo fs2:/baz") - public void testMoveFileFromOneCryptoFileSystemToAnother() throws IOException { - Path file1 = fs1.getPath("/foo"); - Path file2 = fs2.getPath("/baz"); - Assumptions.assumeTrue(Files.isRegularFile(file1)); - Assumptions.assumeTrue(Files.notExists(file2)); - byte[] contents = readAllBytes(file1); - - Files.move(file1, file2); - - Assertions.assertTrue(Files.notExists(file1)); - Assertions.assertTrue(Files.isRegularFile(file2)); - Assertions.assertArrayEquals(contents, readAllBytes(file2)); - } - - } - - - @Nested - public class InMemory { - - private static FileSystem tmpFs; - private static Path pathToVault; - - @BeforeAll - public static void beforeAll() { - tmpFs = Jimfs.newFileSystem(Configuration.unix()); - pathToVault = tmpFs.getPath("/vault"); - } - - @BeforeEach - public void beforeEach() throws IOException { - Files.createDirectory(pathToVault); - } - - @AfterEach - public void afterEach() throws IOException { - try (var paths = Files.walk(pathToVault)) { - var nodes = paths.sorted(Comparator.reverseOrder()).toList(); - for (var node : nodes) { - Files.delete(node); - } - } - } - - @AfterAll - public static void afterAll() throws IOException { - tmpFs.close(); - } - - @Test - @DisplayName("Replace an existing, shortened file") - public void testReplaceExistingShortenedFile() throws IOException { - try (var fs = setupCryptoFs(50, 100, false)) { - var fiftyCharName2 = "/50char2_50char2_50char2_50char2_50char2_50char.txt"; //since filename encryption increases filename length, 50 cleartext chars are sufficient - var source = fs.getPath("/source.txt"); - var target = fs.getPath(fiftyCharName2); - Files.createFile(source); - Files.createFile(target); - - Assertions.assertDoesNotThrow(() -> Files.move(source, target, REPLACE_EXISTING)); - Assertions.assertTrue(Files.notExists(source)); - Assertions.assertTrue(Files.exists(target)); - } - } - - private FileSystem setupCryptoFs(int ciphertextShorteningThreshold, int maxCleartextFilename, boolean readonly) throws IOException { - byte[] key = new byte[64]; - Arrays.fill(key, (byte) 0x55); - var keyLoader = Mockito.mock(MasterkeyLoader.class); - Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(key)); - var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).withShorteningThreshold(ciphertextShorteningThreshold).withMaxCleartextNameLength(maxCleartextFilename).withFlags(readonly ? Set.of(CryptoFileSystemProperties.FileSystemFlags.READONLY) : Set.of()).build(); - CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); - URI fsUri = CryptoFileSystemUri.create(pathToVault); - return FileSystems.newFileSystem(fsUri, cryptoFileSystemProperties().withKeyLoader(keyLoader).build()); - } - - } - - @Nested - @EnabledOnOs({OS.MAC, OS.LINUX}) - @TestInstance(TestInstance.Lifecycle.PER_CLASS) - @DisplayName("On POSIX Systems") - public class PosixTests { - - private FileSystem fs; - - @BeforeAll - public void setup(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFailedException { - Path pathToVault = tmpDir.resolve("vaultDir1"); - Files.createDirectories(pathToVault); - MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); - Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); - var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); - CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); - fs = CryptoFileSystemProvider.newFileSystem(pathToVault, properties); - } - - @Nested - @DisplayName("File Locks") - public class FileLockTests { - - private Path file = fs.getPath("/lock.txt"); - - @BeforeEach - public void setup() throws IOException { - Files.write(file, new byte[100000]); // > 3 * 32k - } - - @Test - @DisplayName("get shared lock on non-readable channel fails") - public void testGetSharedLockOnNonReadableChannel() throws IOException { - try (FileChannel ch = FileChannel.open(file, StandardOpenOption.WRITE)) { - Assertions.assertThrows(NonReadableChannelException.class, () -> { - ch.lock(0, 50000, true); - }); - } - } - - @Test - @DisplayName("locking a closed channel fails") - public void testLockClosedChannel() throws IOException { - FileChannel ch = FileChannel.open(file, StandardOpenOption.WRITE); - ch.close(); - Assertions.assertThrows(ClosedChannelException.class, () -> { - ch.lock(); - }); - } - - @Test - @DisplayName("get exclusive lock on non-writable channel fails") - public void testGetSharedLockOnNonWritableChannel() throws IOException { - try (FileChannel ch = FileChannel.open(file, StandardOpenOption.READ)) { - Assertions.assertThrows(NonWritableChannelException.class, () -> { - ch.lock(0, 50000, false); - }); - } - } - - @ParameterizedTest(name = "shared = {0}") - @CsvSource({"true", "false"}) - @DisplayName("create non-overlapping locks") - public void testNonOverlappingLocks(boolean shared) throws IOException { - try (FileChannel ch = FileChannel.open(file, StandardOpenOption.READ, StandardOpenOption.WRITE)) { - try (FileLock lock1 = ch.lock(0, 10000, shared)) { - try (FileLock lock2 = ch.lock(90000, 10000, shared)) { - Assertions.assertNotSame(lock1, lock2); - } - } - } - } - - @ParameterizedTest(name = "shared = {0}") - @CsvSource({"true", "false"}) - @DisplayName("create overlapping locks") - public void testOverlappingLocks(boolean shared) throws IOException { - try (FileChannel ch = FileChannel.open(file, StandardOpenOption.READ, StandardOpenOption.WRITE)) { - try (FileLock lock1 = ch.lock(0, 10000, shared)) { - // while bock locks cover different cleartext byte ranges, it is necessary to lock the same ciphertext block - Assertions.assertThrows(OverlappingFileLockException.class, () -> { - ch.lock(10000, 10000, shared); - }); - } - } - } - - } - - - } - - @Nested - @EnabledOnOs(OS.WINDOWS) - @TestInstance(TestInstance.Lifecycle.PER_CLASS) - @DisplayName("On Windows Systems") - public class WindowsTests { - - private FileSystem fs; - - @BeforeAll - public void setup(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFailedException { - Path pathToVault = tmpDir.resolve("vaultDir1"); - Files.createDirectories(pathToVault); - MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); - Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); - var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); - CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); - fs = CryptoFileSystemProvider.newFileSystem(pathToVault, properties); - } - - @Test - @DisplayName("set dos attributes") - public void testDosFileAttributes() throws IOException { - Path file = fs.getPath("/msDosAttributes.txt"); - Assumptions.assumeTrue(Files.notExists(file)); - - Files.write(file, new byte[1]); - - Files.setAttribute(file, "dos:hidden", true); - Files.setAttribute(file, "dos:system", true); - Files.setAttribute(file, "dos:archive", true); - Files.setAttribute(file, "dos:readOnly", true); - - Assertions.assertEquals(true, Files.getAttribute(file, "dos:hidden")); - Assertions.assertEquals(true, Files.getAttribute(file, "dos:system")); - Assertions.assertEquals(true, Files.getAttribute(file, "dos:archive")); - Assertions.assertEquals(true, Files.getAttribute(file, "dos:readOnly")); - - Files.setAttribute(file, "dos:hidden", false); - Files.setAttribute(file, "dos:system", false); - Files.setAttribute(file, "dos:archive", false); - Files.setAttribute(file, "dos:readOnly", false); - - Assertions.assertEquals(false, Files.getAttribute(file, "dos:hidden")); - Assertions.assertEquals(false, Files.getAttribute(file, "dos:system")); - Assertions.assertEquals(false, Files.getAttribute(file, "dos:archive")); - Assertions.assertEquals(false, Files.getAttribute(file, "dos:readOnly")); - } - - @Nested - @DisplayName("read-only file") - public class OnReadOnlyFile { - - private Path file = fs.getPath("/readonly.txt"); - private DosFileAttributeView attrView; - - @BeforeEach - public void setup() throws IOException { - Files.write(file, new byte[1]); - - attrView = Files.getFileAttributeView(file, DosFileAttributeView.class); - attrView.setReadOnly(true); - } - - @AfterEach - public void tearDown() throws IOException { - attrView.setReadOnly(false); - } - - @Test - @DisplayName("is not writable") - public void testNotWritable() { - Assertions.assertThrows(AccessDeniedException.class, () -> { - FileChannel.open(file, StandardOpenOption.WRITE); - }); - } - - @Test - @DisplayName("is readable") - public void testReadable() throws IOException { - try (FileChannel ch = FileChannel.open(file, StandardOpenOption.READ)) { - Assertions.assertEquals(1, ch.size()); - } - } - - @Test - @DisplayName("can be made read-write accessible") - public void testFoo() throws IOException { - attrView.setReadOnly(false); - try (FileChannel ch = FileChannel.open(file, StandardOpenOption.WRITE)) { - Assertions.assertEquals(1, ch.size()); - } - } - - } - - - } - -} +/******************************************************************************* + * Copyright (c) 2016 Sebastian Stenzel and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the accompanying LICENSE.txt. + * + * Contributors: + * Sebastian Stenzel - initial API and implementation + *******************************************************************************/ +package org.cryptomator.cryptofs; + +import com.google.common.io.MoreFiles; +import com.google.common.jimfs.Configuration; +import com.google.common.jimfs.Jimfs; +import org.cryptomator.cryptofs.ch.CleartextFileChannel; +import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.api.MasterkeyLoader; +import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mockito; + +import java.io.IOException; +import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.NonReadableChannelException; +import java.nio.channels.NonWritableChannelException; +import java.nio.channels.OverlappingFileLockException; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; +import java.nio.charset.StandardCharsets; +import java.nio.file.AccessDeniedException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.BasicFileAttributeView; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.DosFileAttributeView; +import java.util.Arrays; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.Set; + +import static java.nio.file.Files.readAllBytes; +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; +import static org.cryptomator.cryptofs.CryptoFileSystemProperties.cryptoFileSystemProperties; +import static org.hamcrest.Matchers.is; + + +public class CryptoFileSystemProviderIntegrationTest { + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + public class WithLimitedPaths { + + private MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); + private CryptoFileSystem fs; + private Path shortFilePath; + private Path shortSymlinkPath; + private Path shortDirPath; + + @BeforeAll + public void setup(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFailedException { + Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); + CryptoFileSystemProperties properties = cryptoFileSystemProperties() // + .withFlags() // + .withMasterkeyFilename("masterkey.cryptomator") // + .withKeyLoader(keyLoader) // + .withMaxCleartextNameLength(50) // + .build(); + CryptoFileSystemProvider.initialize(tmpDir, properties, URI.create("test:key")); + fs = CryptoFileSystemProvider.newFileSystem(tmpDir, properties); + } + + @BeforeEach + public void setupEach() throws IOException { + shortFilePath = fs.getPath("/short-enough.txt"); + shortDirPath = fs.getPath("/short-enough-dir"); + shortSymlinkPath = fs.getPath("/symlink.txt"); + Files.createFile(shortFilePath); + Files.createDirectory(shortDirPath); + Files.createSymbolicLink(shortSymlinkPath, shortFilePath); + } + + @AfterEach + public void tearDownEach() throws IOException { + Files.deleteIfExists(shortFilePath); + Files.deleteIfExists(shortDirPath); + Files.deleteIfExists(shortSymlinkPath); + } + + @DisplayName("expect create file to fail with FileNameTooLongException") + @Test + public void testCreateFileExceedingPathLengthLimit() { + Path p = fs.getPath("/this-cleartext-filename-is-longer-than-50-characters"); + Assertions.assertThrows(FileNameTooLongException.class, () -> { + Files.createFile(p); + }); + } + + @DisplayName("expect create directory to fail with FileNameTooLongException") + @Test + public void testCreateDirExceedingPathLengthLimit() { + Path p = fs.getPath("/this-cleartext-filename-is-longer-than-50-characters"); + Assertions.assertThrows(FileNameTooLongException.class, () -> { + Files.createDirectory(p); + }); + } + + @DisplayName("expect create symlink to fail with FileNameTooLongException") + @Test + public void testCreateSymlinkExceedingPathLengthLimit() { + Path p = fs.getPath("/this-cleartext-filename-is-longer-than-50-characters"); + Assertions.assertThrows(FileNameTooLongException.class, () -> { + Files.createSymbolicLink(p, shortFilePath); + }); + } + + @DisplayName("expect move to fail with FileNameTooLongException") + @ParameterizedTest(name = "move {0} -> this-cleartext-filename-is-longer-than-50-characters") + @ValueSource(strings = {"/short-enough.txt", "/short-enough-dir", "/symlink.txt"}) + public void testMoveExceedingPathLengthLimit(String path) { + Path src = fs.getPath(path); + Path dst = fs.getPath("/this-cleartext-filename-is-longer-than-50-characters"); + Assertions.assertThrows(FileNameTooLongException.class, () -> { + Files.move(src, dst); + }); + Assertions.assertTrue(Files.exists(src)); + Assertions.assertTrue(Files.notExists(dst)); + } + + @DisplayName("expect copy to fail with FileNameTooLongException") + @ParameterizedTest(name = "copy {0} -> this-cleartext-filename-is-longer-than-50-characters") + @ValueSource(strings = {"/short-enough.txt", "/short-enough-dir", "/symlink.txt"}) + public void testCopyExceedingPathLengthLimit(String path) { + Path src = fs.getPath(path); + Path dst = fs.getPath("/this-cleartext-filename-is-longer-than-50-characters"); + Assertions.assertThrows(FileNameTooLongException.class, () -> { + Files.copy(src, dst, LinkOption.NOFOLLOW_LINKS); + }); + Assertions.assertTrue(Files.exists(src)); + Assertions.assertTrue(Files.notExists(dst)); + } + + } + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) + public class InMemoryOrdered { + + private FileSystem tmpFs; + private MasterkeyLoader keyLoader1; + private MasterkeyLoader keyLoader2; + private Path pathToVault1; + private Path pathToVault2; + private Path vaultConfigFile1; + private Path vaultConfigFile2; + private FileSystem fs1; + private FileSystem fs2; + + @BeforeAll + public void setup() throws IOException, MasterkeyLoadingFailedException { + tmpFs = Jimfs.newFileSystem(Configuration.unix()); + byte[] key1 = new byte[64]; + byte[] key2 = new byte[64]; + Arrays.fill(key1, (byte) 0x55); + Arrays.fill(key2, (byte) 0x77); + keyLoader1 = Mockito.mock(MasterkeyLoader.class); + keyLoader2 = Mockito.mock(MasterkeyLoader.class); + Mockito.when(keyLoader1.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(key1)); + Mockito.when(keyLoader2.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(key2)); + pathToVault1 = tmpFs.getPath("/vaultDir1"); + pathToVault2 = tmpFs.getPath("/vaultDir2"); + Files.createDirectory(pathToVault1); + Files.createDirectory(pathToVault2); + vaultConfigFile1 = pathToVault1.resolve("vault.cryptomator"); + vaultConfigFile2 = pathToVault2.resolve("vault.cryptomator"); + } + + @AfterAll + public void teardown() throws IOException { + tmpFs.close(); + } + + @Test + @Order(1) + @DisplayName("initialize vaults") + public void initializeVaults() { + Assertions.assertAll(() -> { + var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader1).build(); + CryptoFileSystemProvider.initialize(pathToVault1, properties, URI.create("test:key")); + Assertions.assertTrue(Files.isDirectory(pathToVault1.resolve("d"))); + Assertions.assertTrue(Files.isRegularFile(vaultConfigFile1)); + }, () -> { + var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader2).build(); + CryptoFileSystemProvider.initialize(pathToVault2, properties, URI.create("test:key")); + Assertions.assertTrue(Files.isDirectory(pathToVault2.resolve("d"))); + Assertions.assertTrue(Files.isRegularFile(vaultConfigFile2)); + }); + } + + @Test + @Order(2) + @DisplayName("get filesystem with incorrect credentials") + public void testGetFsWithWrongCredentials() throws IOException { + Assumptions.assumeTrue(CryptoFileSystemProvider.checkDirStructureForVault(pathToVault1, "vault.cryptomator", "masterkey.cryptomator") == DirStructure.VAULT); + Assumptions.assumeTrue(CryptoFileSystemProvider.checkDirStructureForVault(pathToVault2, "vault.cryptomator", "masterkey.cryptomator") == DirStructure.VAULT); + Assertions.assertAll(() -> { + URI fsUri = CryptoFileSystemUri.create(pathToVault1); + CryptoFileSystemProperties properties = cryptoFileSystemProperties() // + .withFlags() // + .withMasterkeyFilename("masterkey.cryptomator") // + .withKeyLoader(keyLoader2) // + .build(); + Assertions.assertThrows(VaultKeyInvalidException.class, () -> { + FileSystems.newFileSystem(fsUri, properties); + }); + }, () -> { + URI fsUri = CryptoFileSystemUri.create(pathToVault2); + CryptoFileSystemProperties properties = cryptoFileSystemProperties() // + .withFlags() // + .withMasterkeyFilename("masterkey.cryptomator") // + .withKeyLoader(keyLoader1) // + .build(); + Assertions.assertThrows(VaultKeyInvalidException.class, () -> { + FileSystems.newFileSystem(fsUri, properties); + }); + }); + } + + @Test + @Order(4) + @DisplayName("get filesystem with correct credentials") + public void testGetFsViaNioApi() { + Assumptions.assumeTrue(Files.exists(vaultConfigFile1)); + Assumptions.assumeTrue(Files.exists(vaultConfigFile2)); + Assertions.assertAll(() -> { + URI fsUri = CryptoFileSystemUri.create(pathToVault1); + fs1 = FileSystems.newFileSystem(fsUri, cryptoFileSystemProperties().withKeyLoader(keyLoader1).build()); + Assertions.assertTrue(fs1 instanceof CryptoFileSystemImpl); + + FileSystem sameFs = FileSystems.getFileSystem(fsUri); + Assertions.assertSame(fs1, sameFs); + }, () -> { + URI fsUri = CryptoFileSystemUri.create(pathToVault2); + fs2 = FileSystems.newFileSystem(fsUri, cryptoFileSystemProperties().withKeyLoader(keyLoader2).build()); + Assertions.assertTrue(fs2 instanceof CryptoFileSystemImpl); + + FileSystem sameFs = FileSystems.getFileSystem(fsUri); + Assertions.assertSame(fs2, sameFs); + }); + } + + @Test + @Order(5) + @DisplayName("touch /foo") + public void testOpenAndCloseFileChannel() throws IOException { + Assumptions.assumeTrue(fs1.isOpen()); + + try (FileChannel ch = FileChannel.open(fs1.getPath("/foo"), EnumSet.of(StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW))) { + Assertions.assertTrue(ch instanceof CleartextFileChannel); + } + } + + @Test + @Order(6) + @DisplayName("ln -s foo /link") + public void testCreateSymlink() { + Path target = fs1.getPath("/foo"); + Assumptions.assumeTrue(Files.isRegularFile(target)); + Path link = fs1.getPath("/link"); + + Assertions.assertDoesNotThrow(() -> { + Files.createSymbolicLink(link, target); + }); + } + + @Test + @Order(7) + @DisplayName("echo 'hello world' > /link") + public void testWriteToSymlink() throws IOException { + Path link = fs1.getPath("/link"); + Assumptions.assumeTrue(Files.isSymbolicLink(link)); + + Assertions.assertDoesNotThrow(() -> { + try (WritableByteChannel ch = Files.newByteChannel(link, StandardOpenOption.WRITE)) { + ch.write(StandardCharsets.US_ASCII.encode("hello world")); + } + }); + } + + @Test + @Order(7) + @DisplayName("cat `readlink -f /link`") + public void testReadFromSymlink() throws IOException { + Path link = fs1.getPath("/link"); + Assumptions.assumeTrue(Files.isSymbolicLink(link)); + Path target = Files.readSymbolicLink(link); + + try (ReadableByteChannel ch = Files.newByteChannel(target, StandardOpenOption.READ)) { + ByteBuffer buf = ByteBuffer.allocate(100); + ch.read(buf); + buf.flip(); + String str = StandardCharsets.US_ASCII.decode(buf).toString(); + Assertions.assertEquals("hello world", str); + } + } + + @Test + @Order(7) + @DisplayName("cp /link /otherlink") + public void testCopySymlinkSymlink() throws IOException { + Path src = fs1.getPath("/link"); + Path dst = fs1.getPath("/otherlink"); + Assumptions.assumeTrue(Files.isSymbolicLink(src)); + Assumptions.assumeTrue(Files.notExists(dst)); + Files.copy(src, dst, LinkOption.NOFOLLOW_LINKS); + Assertions.assertTrue(Files.isSymbolicLink(src)); + Assertions.assertTrue(Files.isSymbolicLink(dst)); + } + + @Test + @Order(8) + @DisplayName("rm /link") + public void testRemoveSymlink() throws IOException { + Path link = fs1.getPath("/link"); + Assumptions.assumeTrue(Files.isSymbolicLink(link)); + + Assertions.assertDoesNotThrow(() -> { + Files.delete(link); + }); + } + + @Test + @Order(8) + @DisplayName("rm /otherlink") + public void testRemoveOtherSymlink() throws IOException { + Path link = fs1.getPath("/otherlink"); + Assumptions.assumeTrue(Files.isSymbolicLink(link)); + + Assertions.assertDoesNotThrow(() -> { + Files.delete(link); + }); + } + + @Test + @Order(9) + @DisplayName("ln -s foo '/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet'") + public void testCreateSymlinkWithLongName() throws IOException { + Path target = fs1.getPath("/foo"); + Assumptions.assumeTrue(Files.isRegularFile(target)); + Path longNameLink = fs1.getPath("/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet"); + Files.createSymbolicLink(longNameLink, target); + MatcherAssert.assertThat(MoreFiles.listFiles(fs1.getPath("/")), Matchers.hasItem(longNameLink)); + Assertions.assertTrue(Files.exists(longNameLink)); + } + + @Test + @Order(10) + @DisplayName("mv '/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet' '/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat") + public void testMoveSymlinkWithLongNameToAnotherLongName() throws IOException { + Path longNameSource = fs1.getPath("/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet"); + Assumptions.assumeTrue(Files.isSymbolicLink(longNameSource)); + Path longNameTarget = longNameSource.resolveSibling("/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat"); + Files.move(longNameSource, longNameTarget); + Assertions.assertTrue(Files.exists(longNameTarget)); + Assertions.assertTrue(Files.notExists(longNameSource)); + } + + @Test + @Order(11) + @DisplayName("rm -r '/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat'") + public void testRemoveSymlinkWithLongName() throws IOException { + Path longNamePath = fs1.getPath("/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat"); + Files.delete(longNamePath); + Assertions.assertTrue(Files.notExists(longNamePath)); + } + + @Test + @Order(12) + @DisplayName("mkdir '/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet'") + public void testCreateDirWithLongName() throws IOException { + Path longNamePath = fs1.getPath("/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet"); + Files.createDirectory(longNamePath); + Assertions.assertTrue(Files.isDirectory(longNamePath)); + MatcherAssert.assertThat(MoreFiles.listFiles(fs1.getPath("/")), Matchers.hasItem(longNamePath)); + } + + @Test + @Order(13) + @DisplayName("mv '/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet' '/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat") + public void testMoveDirWithLongNameToAnotherLongName() throws IOException { + Path longNameSource = fs1.getPath("/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet"); + Path longNameTarget = longNameSource.resolveSibling("/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat"); + Files.move(longNameSource, longNameTarget); + Assertions.assertTrue(Files.exists(longNameTarget)); + Assertions.assertTrue(Files.notExists(longNameSource)); + } + + @Test + @Order(14) + @DisplayName("rm -r '/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat'") + public void testRemoveDirWithLongName() throws IOException { + Path longNamePath = fs1.getPath("/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat"); + Files.delete(longNamePath); + Assertions.assertTrue(Files.notExists(longNamePath)); + } + + @Test + @Order(15) + @DisplayName("touch '/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet'") + public void testCreateFileWithLongName() throws IOException { + Path longNamePath = fs1.getPath("/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet"); + Files.createFile(longNamePath); + Assertions.assertTrue(Files.isRegularFile(longNamePath)); + MatcherAssert.assertThat(MoreFiles.listFiles(fs1.getPath("/")), Matchers.hasItem(longNamePath)); + } + + @Test + @Order(16) + @DisplayName("mv '/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet' '/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat") + public void testMoveFileWithLongNameToAnotherLongName() throws IOException { + Path longNameSource = fs1.getPath("/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet"); + Path longNameTarget = longNameSource.resolveSibling("/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat"); + Files.move(longNameSource, longNameTarget); + Assertions.assertTrue(Files.exists(longNameTarget)); + Assertions.assertTrue(Files.notExists(longNameSource)); + } + + @Test + @Order(17) + @DisplayName("rm -r '/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat'") + public void testRemoveFileWithLongName() throws IOException { + Path longNamePath = fs1.getPath("/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat"); + Files.delete(longNamePath); + Assertions.assertTrue(Files.notExists(longNamePath)); + } + + @Test + @Order(18) + @DisplayName("cp fs1:/foo fs2:/bar") + public void testCopyFileAcrossFilesystem() throws IOException { + Path file1 = fs1.getPath("/foo"); + Path file2 = fs2.getPath("/bar"); + Assumptions.assumeTrue(Files.isRegularFile(file1)); + Assumptions.assumeTrue(Files.notExists(file2)); + + Files.copy(file1, file2); + + Assertions.assertArrayEquals(readAllBytes(file1), readAllBytes(file2)); + } + + @Test + @Order(19) + @DisplayName("echo 'goodbye world' > /foo") + public void testWriteToFile() throws IOException { + Path file1 = fs1.getPath("/foo"); + Assumptions.assumeTrue(Files.isRegularFile(file1)); + + Assertions.assertDoesNotThrow(() -> { + Files.write(file1, "goodbye world".getBytes()); + }); + } + + @Test + @Order(20) + @DisplayName("cp -f fs1:/foo fs2:/bar") + public void testCopyFileAcrossFilesystemReplaceExisting() throws IOException { + Path file1 = fs1.getPath("/foo"); + Path file2 = fs2.getPath("/bar"); + Assumptions.assumeTrue(Files.isRegularFile(file1)); + Assumptions.assumeTrue(Files.isRegularFile(file2)); + + Files.copy(file1, file2, REPLACE_EXISTING); + + Assertions.assertArrayEquals(readAllBytes(file1), readAllBytes(file2)); + } + + @Test + @Order(21) + @DisplayName("readattr /attributes.txt") + public void testLazinessOfFileAttributeViews() throws IOException { + Path file = fs1.getPath("/attributes.txt"); + Assumptions.assumeTrue(Files.notExists(file)); + + BasicFileAttributeView attrView = Files.getFileAttributeView(file, BasicFileAttributeView.class); + Assertions.assertNotNull(attrView); + Assertions.assertThrows(NoSuchFileException.class, () -> { + attrView.readAttributes(); + }); + + Files.write(file, new byte[3], StandardOpenOption.CREATE_NEW); + BasicFileAttributes attrs = attrView.readAttributes(); + Assertions.assertNotNull(attrs); + Assertions.assertEquals(3, attrs.size()); + + Files.delete(file); + Assertions.assertThrows(NoSuchFileException.class, () -> { + attrView.readAttributes(); + }); + Assertions.assertEquals(3, attrs.size()); // attrs should be immutable once they are read. + } + + @Test + @Order(22) + @DisplayName("ln -s /linked/targetY /links/linkX") + public void testSymbolicLinks() throws IOException { + Path linksDir = fs1.getPath("/links"); + Assumptions.assumeTrue(Files.notExists(linksDir)); + Files.createDirectories(linksDir); + + Assertions.assertAll(() -> { + Path link = linksDir.resolve("link1"); + Files.createDirectories(link.getParent()); + Files.createSymbolicLink(link, fs1.getPath("/linked/target1")); + Path target = Files.readSymbolicLink(link); + MatcherAssert.assertThat(target.getFileSystem(), is(link.getFileSystem())); // as per contract of readSymbolicLink + MatcherAssert.assertThat(target.toString(), Matchers.equalTo("/linked/target1")); + MatcherAssert.assertThat(link.resolveSibling(target).toString(), Matchers.equalTo("/linked/target1")); + }, () -> { + Path link = linksDir.resolve("link2"); + Files.createDirectories(link.getParent()); + Files.createSymbolicLink(link, fs1.getPath("./target2")); + Path target = Files.readSymbolicLink(link); + MatcherAssert.assertThat(target.getFileSystem(), is(link.getFileSystem())); + MatcherAssert.assertThat(target.toString(), Matchers.equalTo("./target2")); + MatcherAssert.assertThat(link.resolveSibling(target).normalize().toString(), Matchers.equalTo("/links/target2")); + }, () -> { + Path link = linksDir.resolve("link3"); + Files.createDirectories(link.getParent()); + Files.createSymbolicLink(link, fs1.getPath("../target3")); + Path target = Files.readSymbolicLink(link); + MatcherAssert.assertThat(target.getFileSystem(), is(link.getFileSystem())); + MatcherAssert.assertThat(target.toString(), Matchers.equalTo("../target3")); + MatcherAssert.assertThat(link.resolveSibling(target).normalize().toString(), Matchers.equalTo("/target3")); + }); + } + + @Test + @Order(22) + @DisplayName("mv -f fs1:/foo fs2:/baz") + public void testMoveFileFromOneCryptoFileSystemToAnother() throws IOException { + Path file1 = fs1.getPath("/foo"); + Path file2 = fs2.getPath("/baz"); + Assumptions.assumeTrue(Files.isRegularFile(file1)); + Assumptions.assumeTrue(Files.notExists(file2)); + byte[] contents = readAllBytes(file1); + + Files.move(file1, file2); + + Assertions.assertTrue(Files.notExists(file1)); + Assertions.assertTrue(Files.isRegularFile(file2)); + Assertions.assertArrayEquals(contents, readAllBytes(file2)); + } + + } + + + @Nested + public class InMemory { + + private static FileSystem tmpFs; + private static Path pathToVault; + + @BeforeAll + public static void beforeAll() { + tmpFs = Jimfs.newFileSystem(Configuration.unix()); + pathToVault = tmpFs.getPath("/vault"); + } + + @BeforeEach + public void beforeEach() throws IOException { + Files.createDirectory(pathToVault); + } + + @AfterEach + public void afterEach() throws IOException { + try (var paths = Files.walk(pathToVault)) { + var nodes = paths.sorted(Comparator.reverseOrder()).toList(); + for (var node : nodes) { + Files.delete(node); + } + } + } + + @AfterAll + public static void afterAll() throws IOException { + tmpFs.close(); + } + + @Test + @DisplayName("Replace an existing, shortened file") + public void testReplaceExistingShortenedFile() throws IOException { + try (var fs = setupCryptoFs(50, 100, false)) { + var fiftyCharName2 = "/50char2_50char2_50char2_50char2_50char2_50char.txt"; //since filename encryption increases filename length, 50 cleartext chars are sufficient + var source = fs.getPath("/source.txt"); + var target = fs.getPath(fiftyCharName2); + Files.createFile(source); + Files.createFile(target); + + Assertions.assertDoesNotThrow(() -> Files.move(source, target, REPLACE_EXISTING)); + Assertions.assertTrue(Files.notExists(source)); + Assertions.assertTrue(Files.exists(target)); + } + } + + private FileSystem setupCryptoFs(int ciphertextShorteningThreshold, int maxCleartextFilename, boolean readonly) throws IOException { + byte[] key = new byte[64]; + Arrays.fill(key, (byte) 0x55); + var keyLoader = Mockito.mock(MasterkeyLoader.class); + Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(key)); + var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).withShorteningThreshold(ciphertextShorteningThreshold).withMaxCleartextNameLength(maxCleartextFilename).withFlags(readonly ? Set.of(CryptoFileSystemProperties.FileSystemFlags.READONLY) : Set.of()).build(); + CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); + URI fsUri = CryptoFileSystemUri.create(pathToVault); + return FileSystems.newFileSystem(fsUri, cryptoFileSystemProperties().withKeyLoader(keyLoader).build()); + } + + } + + @Nested + @EnabledOnOs({OS.MAC, OS.LINUX}) + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + @DisplayName("On POSIX Systems") + public class PosixTests { + + private FileSystem fs; + + @BeforeAll + public void setup(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFailedException { + Path pathToVault = tmpDir.resolve("vaultDir1"); + Files.createDirectories(pathToVault); + MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); + Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); + var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); + CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); + fs = CryptoFileSystemProvider.newFileSystem(pathToVault, properties); + } + + @Nested + @DisplayName("File Locks") + public class FileLockTests { + + private Path file = fs.getPath("/lock.txt"); + + @BeforeEach + public void setup() throws IOException { + Files.write(file, new byte[100000]); // > 3 * 32k + } + + @Test + @DisplayName("get shared lock on non-readable channel fails") + public void testGetSharedLockOnNonReadableChannel() throws IOException { + try (FileChannel ch = FileChannel.open(file, StandardOpenOption.WRITE)) { + Assertions.assertThrows(NonReadableChannelException.class, () -> { + ch.lock(0, 50000, true); + }); + } + } + + @Test + @DisplayName("locking a closed channel fails") + public void testLockClosedChannel() throws IOException { + FileChannel ch = FileChannel.open(file, StandardOpenOption.WRITE); + ch.close(); + Assertions.assertThrows(ClosedChannelException.class, () -> { + ch.lock(); + }); + } + + @Test + @DisplayName("get exclusive lock on non-writable channel fails") + public void testGetSharedLockOnNonWritableChannel() throws IOException { + try (FileChannel ch = FileChannel.open(file, StandardOpenOption.READ)) { + Assertions.assertThrows(NonWritableChannelException.class, () -> { + ch.lock(0, 50000, false); + }); + } + } + + @ParameterizedTest(name = "shared = {0}") + @CsvSource({"true", "false"}) + @DisplayName("create non-overlapping locks") + public void testNonOverlappingLocks(boolean shared) throws IOException { + try (FileChannel ch = FileChannel.open(file, StandardOpenOption.READ, StandardOpenOption.WRITE)) { + try (FileLock lock1 = ch.lock(0, 10000, shared)) { + try (FileLock lock2 = ch.lock(90000, 10000, shared)) { + Assertions.assertNotSame(lock1, lock2); + } + } + } + } + + @ParameterizedTest(name = "shared = {0}") + @CsvSource({"true", "false"}) + @DisplayName("create overlapping locks") + public void testOverlappingLocks(boolean shared) throws IOException { + try (FileChannel ch = FileChannel.open(file, StandardOpenOption.READ, StandardOpenOption.WRITE)) { + try (FileLock lock1 = ch.lock(0, 10000, shared)) { + // while bock locks cover different cleartext byte ranges, it is necessary to lock the same ciphertext block + Assertions.assertThrows(OverlappingFileLockException.class, () -> { + ch.lock(10000, 10000, shared); + }); + } + } + } + + } + + + } + + @Nested + @EnabledOnOs(OS.WINDOWS) + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + @DisplayName("On Windows Systems") + public class WindowsTests { + + private FileSystem fs; + + @BeforeAll + public void setup(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFailedException { + Path pathToVault = tmpDir.resolve("vaultDir1"); + Files.createDirectories(pathToVault); + MasterkeyLoader keyLoader = Mockito.mock(MasterkeyLoader.class); + Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(new byte[64])); + var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).build(); + CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); + fs = CryptoFileSystemProvider.newFileSystem(pathToVault, properties); + } + + @Test + @DisplayName("set dos attributes") + public void testDosFileAttributes() throws IOException { + Path file = fs.getPath("/msDosAttributes.txt"); + Assumptions.assumeTrue(Files.notExists(file)); + + Files.write(file, new byte[1]); + + Files.setAttribute(file, "dos:hidden", true); + Files.setAttribute(file, "dos:system", true); + Files.setAttribute(file, "dos:archive", true); + Files.setAttribute(file, "dos:readOnly", true); + + Assertions.assertEquals(true, Files.getAttribute(file, "dos:hidden")); + Assertions.assertEquals(true, Files.getAttribute(file, "dos:system")); + Assertions.assertEquals(true, Files.getAttribute(file, "dos:archive")); + Assertions.assertEquals(true, Files.getAttribute(file, "dos:readOnly")); + + Files.setAttribute(file, "dos:hidden", false); + Files.setAttribute(file, "dos:system", false); + Files.setAttribute(file, "dos:archive", false); + Files.setAttribute(file, "dos:readOnly", false); + + Assertions.assertEquals(false, Files.getAttribute(file, "dos:hidden")); + Assertions.assertEquals(false, Files.getAttribute(file, "dos:system")); + Assertions.assertEquals(false, Files.getAttribute(file, "dos:archive")); + Assertions.assertEquals(false, Files.getAttribute(file, "dos:readOnly")); + } + + @Nested + @DisplayName("read-only file") + public class OnReadOnlyFile { + + private Path file = fs.getPath("/readonly.txt"); + private DosFileAttributeView attrView; + + @BeforeEach + public void setup() throws IOException { + Files.write(file, new byte[1]); + + attrView = Files.getFileAttributeView(file, DosFileAttributeView.class); + attrView.setReadOnly(true); + } + + @AfterEach + public void tearDown() throws IOException { + attrView.setReadOnly(false); + } + + @Test + @DisplayName("is not writable") + public void testNotWritable() { + Assertions.assertThrows(AccessDeniedException.class, () -> { + FileChannel.open(file, StandardOpenOption.WRITE); + }); + } + + @Test + @DisplayName("is readable") + public void testReadable() throws IOException { + try (FileChannel ch = FileChannel.open(file, StandardOpenOption.READ)) { + Assertions.assertEquals(1, ch.size()); + } + } + + @Test + @DisplayName("can be made read-write accessible") + public void testFoo() throws IOException { + attrView.setReadOnly(false); + try (FileChannel ch = FileChannel.open(file, StandardOpenOption.WRITE)) { + Assertions.assertEquals(1, ch.size()); + } + } + + } + + + } + +} From fbe79f76f7ee9f288f81d085f151aaa5ae91a360 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Sat, 29 Jul 2023 16:20:39 +0200 Subject: [PATCH 03/11] Added static imports --- ...yptoFileSystemProviderIntegrationTest.java | 208 +++++++++--------- 1 file changed, 108 insertions(+), 100 deletions(-) diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java index 36a9fc4d..2f6e1b5b 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java @@ -19,8 +19,6 @@ import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -70,6 +68,16 @@ import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static org.cryptomator.cryptofs.CryptoFileSystemProperties.cryptoFileSystemProperties; import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; public class CryptoFileSystemProviderIntegrationTest { @@ -118,7 +126,7 @@ public void tearDownEach() throws IOException { @Test public void testCreateFileExceedingPathLengthLimit() { Path p = fs.getPath("/this-cleartext-filename-is-longer-than-50-characters"); - Assertions.assertThrows(FileNameTooLongException.class, () -> { + assertThrows(FileNameTooLongException.class, () -> { Files.createFile(p); }); } @@ -127,7 +135,7 @@ public void testCreateFileExceedingPathLengthLimit() { @Test public void testCreateDirExceedingPathLengthLimit() { Path p = fs.getPath("/this-cleartext-filename-is-longer-than-50-characters"); - Assertions.assertThrows(FileNameTooLongException.class, () -> { + assertThrows(FileNameTooLongException.class, () -> { Files.createDirectory(p); }); } @@ -136,7 +144,7 @@ public void testCreateDirExceedingPathLengthLimit() { @Test public void testCreateSymlinkExceedingPathLengthLimit() { Path p = fs.getPath("/this-cleartext-filename-is-longer-than-50-characters"); - Assertions.assertThrows(FileNameTooLongException.class, () -> { + assertThrows(FileNameTooLongException.class, () -> { Files.createSymbolicLink(p, shortFilePath); }); } @@ -147,11 +155,11 @@ public void testCreateSymlinkExceedingPathLengthLimit() { public void testMoveExceedingPathLengthLimit(String path) { Path src = fs.getPath(path); Path dst = fs.getPath("/this-cleartext-filename-is-longer-than-50-characters"); - Assertions.assertThrows(FileNameTooLongException.class, () -> { + assertThrows(FileNameTooLongException.class, () -> { Files.move(src, dst); }); - Assertions.assertTrue(Files.exists(src)); - Assertions.assertTrue(Files.notExists(dst)); + assertTrue(Files.exists(src)); + assertTrue(Files.notExists(dst)); } @DisplayName("expect copy to fail with FileNameTooLongException") @@ -160,11 +168,11 @@ public void testMoveExceedingPathLengthLimit(String path) { public void testCopyExceedingPathLengthLimit(String path) { Path src = fs.getPath(path); Path dst = fs.getPath("/this-cleartext-filename-is-longer-than-50-characters"); - Assertions.assertThrows(FileNameTooLongException.class, () -> { + assertThrows(FileNameTooLongException.class, () -> { Files.copy(src, dst, LinkOption.NOFOLLOW_LINKS); }); - Assertions.assertTrue(Files.exists(src)); - Assertions.assertTrue(Files.notExists(dst)); + assertTrue(Files.exists(src)); + assertTrue(Files.notExists(dst)); } } @@ -212,16 +220,16 @@ public void teardown() throws IOException { @Order(1) @DisplayName("initialize vaults") public void initializeVaults() { - Assertions.assertAll(() -> { + assertAll(() -> { var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader1).build(); CryptoFileSystemProvider.initialize(pathToVault1, properties, URI.create("test:key")); - Assertions.assertTrue(Files.isDirectory(pathToVault1.resolve("d"))); - Assertions.assertTrue(Files.isRegularFile(vaultConfigFile1)); + assertTrue(Files.isDirectory(pathToVault1.resolve("d"))); + assertTrue(Files.isRegularFile(vaultConfigFile1)); }, () -> { var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader2).build(); CryptoFileSystemProvider.initialize(pathToVault2, properties, URI.create("test:key")); - Assertions.assertTrue(Files.isDirectory(pathToVault2.resolve("d"))); - Assertions.assertTrue(Files.isRegularFile(vaultConfigFile2)); + assertTrue(Files.isDirectory(pathToVault2.resolve("d"))); + assertTrue(Files.isRegularFile(vaultConfigFile2)); }); } @@ -229,16 +237,16 @@ public void initializeVaults() { @Order(2) @DisplayName("get filesystem with incorrect credentials") public void testGetFsWithWrongCredentials() throws IOException { - Assumptions.assumeTrue(CryptoFileSystemProvider.checkDirStructureForVault(pathToVault1, "vault.cryptomator", "masterkey.cryptomator") == DirStructure.VAULT); - Assumptions.assumeTrue(CryptoFileSystemProvider.checkDirStructureForVault(pathToVault2, "vault.cryptomator", "masterkey.cryptomator") == DirStructure.VAULT); - Assertions.assertAll(() -> { + assumeTrue(CryptoFileSystemProvider.checkDirStructureForVault(pathToVault1, "vault.cryptomator", "masterkey.cryptomator") == DirStructure.VAULT); + assumeTrue(CryptoFileSystemProvider.checkDirStructureForVault(pathToVault2, "vault.cryptomator", "masterkey.cryptomator") == DirStructure.VAULT); + assertAll(() -> { URI fsUri = CryptoFileSystemUri.create(pathToVault1); CryptoFileSystemProperties properties = cryptoFileSystemProperties() // .withFlags() // .withMasterkeyFilename("masterkey.cryptomator") // .withKeyLoader(keyLoader2) // .build(); - Assertions.assertThrows(VaultKeyInvalidException.class, () -> { + assertThrows(VaultKeyInvalidException.class, () -> { FileSystems.newFileSystem(fsUri, properties); }); }, () -> { @@ -248,7 +256,7 @@ public void testGetFsWithWrongCredentials() throws IOException { .withMasterkeyFilename("masterkey.cryptomator") // .withKeyLoader(keyLoader1) // .build(); - Assertions.assertThrows(VaultKeyInvalidException.class, () -> { + assertThrows(VaultKeyInvalidException.class, () -> { FileSystems.newFileSystem(fsUri, properties); }); }); @@ -258,22 +266,22 @@ public void testGetFsWithWrongCredentials() throws IOException { @Order(4) @DisplayName("get filesystem with correct credentials") public void testGetFsViaNioApi() { - Assumptions.assumeTrue(Files.exists(vaultConfigFile1)); - Assumptions.assumeTrue(Files.exists(vaultConfigFile2)); - Assertions.assertAll(() -> { + assumeTrue(Files.exists(vaultConfigFile1)); + assumeTrue(Files.exists(vaultConfigFile2)); + assertAll(() -> { URI fsUri = CryptoFileSystemUri.create(pathToVault1); fs1 = FileSystems.newFileSystem(fsUri, cryptoFileSystemProperties().withKeyLoader(keyLoader1).build()); - Assertions.assertTrue(fs1 instanceof CryptoFileSystemImpl); + assertTrue(fs1 instanceof CryptoFileSystemImpl); FileSystem sameFs = FileSystems.getFileSystem(fsUri); - Assertions.assertSame(fs1, sameFs); + assertSame(fs1, sameFs); }, () -> { URI fsUri = CryptoFileSystemUri.create(pathToVault2); fs2 = FileSystems.newFileSystem(fsUri, cryptoFileSystemProperties().withKeyLoader(keyLoader2).build()); - Assertions.assertTrue(fs2 instanceof CryptoFileSystemImpl); + assertTrue(fs2 instanceof CryptoFileSystemImpl); FileSystem sameFs = FileSystems.getFileSystem(fsUri); - Assertions.assertSame(fs2, sameFs); + assertSame(fs2, sameFs); }); } @@ -281,10 +289,10 @@ public void testGetFsViaNioApi() { @Order(5) @DisplayName("touch /foo") public void testOpenAndCloseFileChannel() throws IOException { - Assumptions.assumeTrue(fs1.isOpen()); + assumeTrue(fs1.isOpen()); try (FileChannel ch = FileChannel.open(fs1.getPath("/foo"), EnumSet.of(StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW))) { - Assertions.assertTrue(ch instanceof CleartextFileChannel); + assertTrue(ch instanceof CleartextFileChannel); } } @@ -293,10 +301,10 @@ public void testOpenAndCloseFileChannel() throws IOException { @DisplayName("ln -s foo /link") public void testCreateSymlink() { Path target = fs1.getPath("/foo"); - Assumptions.assumeTrue(Files.isRegularFile(target)); + assumeTrue(Files.isRegularFile(target)); Path link = fs1.getPath("/link"); - Assertions.assertDoesNotThrow(() -> { + assertDoesNotThrow(() -> { Files.createSymbolicLink(link, target); }); } @@ -306,9 +314,9 @@ public void testCreateSymlink() { @DisplayName("echo 'hello world' > /link") public void testWriteToSymlink() throws IOException { Path link = fs1.getPath("/link"); - Assumptions.assumeTrue(Files.isSymbolicLink(link)); + assumeTrue(Files.isSymbolicLink(link)); - Assertions.assertDoesNotThrow(() -> { + assertDoesNotThrow(() -> { try (WritableByteChannel ch = Files.newByteChannel(link, StandardOpenOption.WRITE)) { ch.write(StandardCharsets.US_ASCII.encode("hello world")); } @@ -320,7 +328,7 @@ public void testWriteToSymlink() throws IOException { @DisplayName("cat `readlink -f /link`") public void testReadFromSymlink() throws IOException { Path link = fs1.getPath("/link"); - Assumptions.assumeTrue(Files.isSymbolicLink(link)); + assumeTrue(Files.isSymbolicLink(link)); Path target = Files.readSymbolicLink(link); try (ReadableByteChannel ch = Files.newByteChannel(target, StandardOpenOption.READ)) { @@ -328,7 +336,7 @@ public void testReadFromSymlink() throws IOException { ch.read(buf); buf.flip(); String str = StandardCharsets.US_ASCII.decode(buf).toString(); - Assertions.assertEquals("hello world", str); + assertEquals("hello world", str); } } @@ -338,11 +346,11 @@ public void testReadFromSymlink() throws IOException { public void testCopySymlinkSymlink() throws IOException { Path src = fs1.getPath("/link"); Path dst = fs1.getPath("/otherlink"); - Assumptions.assumeTrue(Files.isSymbolicLink(src)); - Assumptions.assumeTrue(Files.notExists(dst)); + assumeTrue(Files.isSymbolicLink(src)); + assumeTrue(Files.notExists(dst)); Files.copy(src, dst, LinkOption.NOFOLLOW_LINKS); - Assertions.assertTrue(Files.isSymbolicLink(src)); - Assertions.assertTrue(Files.isSymbolicLink(dst)); + assertTrue(Files.isSymbolicLink(src)); + assertTrue(Files.isSymbolicLink(dst)); } @Test @@ -350,9 +358,9 @@ public void testCopySymlinkSymlink() throws IOException { @DisplayName("rm /link") public void testRemoveSymlink() throws IOException { Path link = fs1.getPath("/link"); - Assumptions.assumeTrue(Files.isSymbolicLink(link)); + assumeTrue(Files.isSymbolicLink(link)); - Assertions.assertDoesNotThrow(() -> { + assertDoesNotThrow(() -> { Files.delete(link); }); } @@ -362,9 +370,9 @@ public void testRemoveSymlink() throws IOException { @DisplayName("rm /otherlink") public void testRemoveOtherSymlink() throws IOException { Path link = fs1.getPath("/otherlink"); - Assumptions.assumeTrue(Files.isSymbolicLink(link)); + assumeTrue(Files.isSymbolicLink(link)); - Assertions.assertDoesNotThrow(() -> { + assertDoesNotThrow(() -> { Files.delete(link); }); } @@ -374,11 +382,11 @@ public void testRemoveOtherSymlink() throws IOException { @DisplayName("ln -s foo '/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet'") public void testCreateSymlinkWithLongName() throws IOException { Path target = fs1.getPath("/foo"); - Assumptions.assumeTrue(Files.isRegularFile(target)); + assumeTrue(Files.isRegularFile(target)); Path longNameLink = fs1.getPath("/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet"); Files.createSymbolicLink(longNameLink, target); MatcherAssert.assertThat(MoreFiles.listFiles(fs1.getPath("/")), Matchers.hasItem(longNameLink)); - Assertions.assertTrue(Files.exists(longNameLink)); + assertTrue(Files.exists(longNameLink)); } @Test @@ -386,11 +394,11 @@ public void testCreateSymlinkWithLongName() throws IOException { @DisplayName("mv '/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet' '/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat") public void testMoveSymlinkWithLongNameToAnotherLongName() throws IOException { Path longNameSource = fs1.getPath("/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet"); - Assumptions.assumeTrue(Files.isSymbolicLink(longNameSource)); + assumeTrue(Files.isSymbolicLink(longNameSource)); Path longNameTarget = longNameSource.resolveSibling("/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat"); Files.move(longNameSource, longNameTarget); - Assertions.assertTrue(Files.exists(longNameTarget)); - Assertions.assertTrue(Files.notExists(longNameSource)); + assertTrue(Files.exists(longNameTarget)); + assertTrue(Files.notExists(longNameSource)); } @Test @@ -399,7 +407,7 @@ public void testMoveSymlinkWithLongNameToAnotherLongName() throws IOException { public void testRemoveSymlinkWithLongName() throws IOException { Path longNamePath = fs1.getPath("/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat"); Files.delete(longNamePath); - Assertions.assertTrue(Files.notExists(longNamePath)); + assertTrue(Files.notExists(longNamePath)); } @Test @@ -408,7 +416,7 @@ public void testRemoveSymlinkWithLongName() throws IOException { public void testCreateDirWithLongName() throws IOException { Path longNamePath = fs1.getPath("/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet"); Files.createDirectory(longNamePath); - Assertions.assertTrue(Files.isDirectory(longNamePath)); + assertTrue(Files.isDirectory(longNamePath)); MatcherAssert.assertThat(MoreFiles.listFiles(fs1.getPath("/")), Matchers.hasItem(longNamePath)); } @@ -419,8 +427,8 @@ public void testMoveDirWithLongNameToAnotherLongName() throws IOException { Path longNameSource = fs1.getPath("/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet"); Path longNameTarget = longNameSource.resolveSibling("/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat"); Files.move(longNameSource, longNameTarget); - Assertions.assertTrue(Files.exists(longNameTarget)); - Assertions.assertTrue(Files.notExists(longNameSource)); + assertTrue(Files.exists(longNameTarget)); + assertTrue(Files.notExists(longNameSource)); } @Test @@ -429,7 +437,7 @@ public void testMoveDirWithLongNameToAnotherLongName() throws IOException { public void testRemoveDirWithLongName() throws IOException { Path longNamePath = fs1.getPath("/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat"); Files.delete(longNamePath); - Assertions.assertTrue(Files.notExists(longNamePath)); + assertTrue(Files.notExists(longNamePath)); } @Test @@ -438,7 +446,7 @@ public void testRemoveDirWithLongName() throws IOException { public void testCreateFileWithLongName() throws IOException { Path longNamePath = fs1.getPath("/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet"); Files.createFile(longNamePath); - Assertions.assertTrue(Files.isRegularFile(longNamePath)); + assertTrue(Files.isRegularFile(longNamePath)); MatcherAssert.assertThat(MoreFiles.listFiles(fs1.getPath("/")), Matchers.hasItem(longNamePath)); } @@ -449,8 +457,8 @@ public void testMoveFileWithLongNameToAnotherLongName() throws IOException { Path longNameSource = fs1.getPath("/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet"); Path longNameTarget = longNameSource.resolveSibling("/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat"); Files.move(longNameSource, longNameTarget); - Assertions.assertTrue(Files.exists(longNameTarget)); - Assertions.assertTrue(Files.notExists(longNameSource)); + assertTrue(Files.exists(longNameTarget)); + assertTrue(Files.notExists(longNameSource)); } @Test @@ -459,7 +467,7 @@ public void testMoveFileWithLongNameToAnotherLongName() throws IOException { public void testRemoveFileWithLongName() throws IOException { Path longNamePath = fs1.getPath("/Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat Talafan Anargaa Wassar Wabsaatangaraffal Bas Bahn Maatwagan Antarnat"); Files.delete(longNamePath); - Assertions.assertTrue(Files.notExists(longNamePath)); + assertTrue(Files.notExists(longNamePath)); } @Test @@ -468,12 +476,12 @@ public void testRemoveFileWithLongName() throws IOException { public void testCopyFileAcrossFilesystem() throws IOException { Path file1 = fs1.getPath("/foo"); Path file2 = fs2.getPath("/bar"); - Assumptions.assumeTrue(Files.isRegularFile(file1)); - Assumptions.assumeTrue(Files.notExists(file2)); + assumeTrue(Files.isRegularFile(file1)); + assumeTrue(Files.notExists(file2)); Files.copy(file1, file2); - Assertions.assertArrayEquals(readAllBytes(file1), readAllBytes(file2)); + assertArrayEquals(readAllBytes(file1), readAllBytes(file2)); } @Test @@ -481,9 +489,9 @@ public void testCopyFileAcrossFilesystem() throws IOException { @DisplayName("echo 'goodbye world' > /foo") public void testWriteToFile() throws IOException { Path file1 = fs1.getPath("/foo"); - Assumptions.assumeTrue(Files.isRegularFile(file1)); + assumeTrue(Files.isRegularFile(file1)); - Assertions.assertDoesNotThrow(() -> { + assertDoesNotThrow(() -> { Files.write(file1, "goodbye world".getBytes()); }); } @@ -494,12 +502,12 @@ public void testWriteToFile() throws IOException { public void testCopyFileAcrossFilesystemReplaceExisting() throws IOException { Path file1 = fs1.getPath("/foo"); Path file2 = fs2.getPath("/bar"); - Assumptions.assumeTrue(Files.isRegularFile(file1)); - Assumptions.assumeTrue(Files.isRegularFile(file2)); + assumeTrue(Files.isRegularFile(file1)); + assumeTrue(Files.isRegularFile(file2)); Files.copy(file1, file2, REPLACE_EXISTING); - Assertions.assertArrayEquals(readAllBytes(file1), readAllBytes(file2)); + assertArrayEquals(readAllBytes(file1), readAllBytes(file2)); } @Test @@ -507,24 +515,24 @@ public void testCopyFileAcrossFilesystemReplaceExisting() throws IOException { @DisplayName("readattr /attributes.txt") public void testLazinessOfFileAttributeViews() throws IOException { Path file = fs1.getPath("/attributes.txt"); - Assumptions.assumeTrue(Files.notExists(file)); + assumeTrue(Files.notExists(file)); BasicFileAttributeView attrView = Files.getFileAttributeView(file, BasicFileAttributeView.class); - Assertions.assertNotNull(attrView); - Assertions.assertThrows(NoSuchFileException.class, () -> { + assertNotNull(attrView); + assertThrows(NoSuchFileException.class, () -> { attrView.readAttributes(); }); Files.write(file, new byte[3], StandardOpenOption.CREATE_NEW); BasicFileAttributes attrs = attrView.readAttributes(); - Assertions.assertNotNull(attrs); - Assertions.assertEquals(3, attrs.size()); + assertNotNull(attrs); + assertEquals(3, attrs.size()); Files.delete(file); - Assertions.assertThrows(NoSuchFileException.class, () -> { + assertThrows(NoSuchFileException.class, () -> { attrView.readAttributes(); }); - Assertions.assertEquals(3, attrs.size()); // attrs should be immutable once they are read. + assertEquals(3, attrs.size()); // attrs should be immutable once they are read. } @Test @@ -532,10 +540,10 @@ public void testLazinessOfFileAttributeViews() throws IOException { @DisplayName("ln -s /linked/targetY /links/linkX") public void testSymbolicLinks() throws IOException { Path linksDir = fs1.getPath("/links"); - Assumptions.assumeTrue(Files.notExists(linksDir)); + assumeTrue(Files.notExists(linksDir)); Files.createDirectories(linksDir); - Assertions.assertAll(() -> { + assertAll(() -> { Path link = linksDir.resolve("link1"); Files.createDirectories(link.getParent()); Files.createSymbolicLink(link, fs1.getPath("/linked/target1")); @@ -568,15 +576,15 @@ public void testSymbolicLinks() throws IOException { public void testMoveFileFromOneCryptoFileSystemToAnother() throws IOException { Path file1 = fs1.getPath("/foo"); Path file2 = fs2.getPath("/baz"); - Assumptions.assumeTrue(Files.isRegularFile(file1)); - Assumptions.assumeTrue(Files.notExists(file2)); + assumeTrue(Files.isRegularFile(file1)); + assumeTrue(Files.notExists(file2)); byte[] contents = readAllBytes(file1); Files.move(file1, file2); - Assertions.assertTrue(Files.notExists(file1)); - Assertions.assertTrue(Files.isRegularFile(file2)); - Assertions.assertArrayEquals(contents, readAllBytes(file2)); + assertTrue(Files.notExists(file1)); + assertTrue(Files.isRegularFile(file2)); + assertArrayEquals(contents, readAllBytes(file2)); } } @@ -624,9 +632,9 @@ public void testReplaceExistingShortenedFile() throws IOException { Files.createFile(source); Files.createFile(target); - Assertions.assertDoesNotThrow(() -> Files.move(source, target, REPLACE_EXISTING)); - Assertions.assertTrue(Files.notExists(source)); - Assertions.assertTrue(Files.exists(target)); + assertDoesNotThrow(() -> Files.move(source, target, REPLACE_EXISTING)); + assertTrue(Files.notExists(source)); + assertTrue(Files.exists(target)); } } @@ -677,7 +685,7 @@ public void setup() throws IOException { @DisplayName("get shared lock on non-readable channel fails") public void testGetSharedLockOnNonReadableChannel() throws IOException { try (FileChannel ch = FileChannel.open(file, StandardOpenOption.WRITE)) { - Assertions.assertThrows(NonReadableChannelException.class, () -> { + assertThrows(NonReadableChannelException.class, () -> { ch.lock(0, 50000, true); }); } @@ -688,7 +696,7 @@ public void testGetSharedLockOnNonReadableChannel() throws IOException { public void testLockClosedChannel() throws IOException { FileChannel ch = FileChannel.open(file, StandardOpenOption.WRITE); ch.close(); - Assertions.assertThrows(ClosedChannelException.class, () -> { + assertThrows(ClosedChannelException.class, () -> { ch.lock(); }); } @@ -697,7 +705,7 @@ public void testLockClosedChannel() throws IOException { @DisplayName("get exclusive lock on non-writable channel fails") public void testGetSharedLockOnNonWritableChannel() throws IOException { try (FileChannel ch = FileChannel.open(file, StandardOpenOption.READ)) { - Assertions.assertThrows(NonWritableChannelException.class, () -> { + assertThrows(NonWritableChannelException.class, () -> { ch.lock(0, 50000, false); }); } @@ -710,7 +718,7 @@ public void testNonOverlappingLocks(boolean shared) throws IOException { try (FileChannel ch = FileChannel.open(file, StandardOpenOption.READ, StandardOpenOption.WRITE)) { try (FileLock lock1 = ch.lock(0, 10000, shared)) { try (FileLock lock2 = ch.lock(90000, 10000, shared)) { - Assertions.assertNotSame(lock1, lock2); + assertNotSame(lock1, lock2); } } } @@ -723,7 +731,7 @@ public void testOverlappingLocks(boolean shared) throws IOException { try (FileChannel ch = FileChannel.open(file, StandardOpenOption.READ, StandardOpenOption.WRITE)) { try (FileLock lock1 = ch.lock(0, 10000, shared)) { // while bock locks cover different cleartext byte ranges, it is necessary to lock the same ciphertext block - Assertions.assertThrows(OverlappingFileLockException.class, () -> { + assertThrows(OverlappingFileLockException.class, () -> { ch.lock(10000, 10000, shared); }); } @@ -758,7 +766,7 @@ public void setup(@TempDir Path tmpDir) throws IOException, MasterkeyLoadingFail @DisplayName("set dos attributes") public void testDosFileAttributes() throws IOException { Path file = fs.getPath("/msDosAttributes.txt"); - Assumptions.assumeTrue(Files.notExists(file)); + assumeTrue(Files.notExists(file)); Files.write(file, new byte[1]); @@ -767,20 +775,20 @@ public void testDosFileAttributes() throws IOException { Files.setAttribute(file, "dos:archive", true); Files.setAttribute(file, "dos:readOnly", true); - Assertions.assertEquals(true, Files.getAttribute(file, "dos:hidden")); - Assertions.assertEquals(true, Files.getAttribute(file, "dos:system")); - Assertions.assertEquals(true, Files.getAttribute(file, "dos:archive")); - Assertions.assertEquals(true, Files.getAttribute(file, "dos:readOnly")); + assertEquals(true, Files.getAttribute(file, "dos:hidden")); + assertEquals(true, Files.getAttribute(file, "dos:system")); + assertEquals(true, Files.getAttribute(file, "dos:archive")); + assertEquals(true, Files.getAttribute(file, "dos:readOnly")); Files.setAttribute(file, "dos:hidden", false); Files.setAttribute(file, "dos:system", false); Files.setAttribute(file, "dos:archive", false); Files.setAttribute(file, "dos:readOnly", false); - Assertions.assertEquals(false, Files.getAttribute(file, "dos:hidden")); - Assertions.assertEquals(false, Files.getAttribute(file, "dos:system")); - Assertions.assertEquals(false, Files.getAttribute(file, "dos:archive")); - Assertions.assertEquals(false, Files.getAttribute(file, "dos:readOnly")); + assertEquals(false, Files.getAttribute(file, "dos:hidden")); + assertEquals(false, Files.getAttribute(file, "dos:system")); + assertEquals(false, Files.getAttribute(file, "dos:archive")); + assertEquals(false, Files.getAttribute(file, "dos:readOnly")); } @Nested @@ -806,7 +814,7 @@ public void tearDown() throws IOException { @Test @DisplayName("is not writable") public void testNotWritable() { - Assertions.assertThrows(AccessDeniedException.class, () -> { + assertThrows(AccessDeniedException.class, () -> { FileChannel.open(file, StandardOpenOption.WRITE); }); } @@ -815,7 +823,7 @@ public void testNotWritable() { @DisplayName("is readable") public void testReadable() throws IOException { try (FileChannel ch = FileChannel.open(file, StandardOpenOption.READ)) { - Assertions.assertEquals(1, ch.size()); + assertEquals(1, ch.size()); } } @@ -824,7 +832,7 @@ public void testReadable() throws IOException { public void testFoo() throws IOException { attrView.setReadOnly(false); try (FileChannel ch = FileChannel.open(file, StandardOpenOption.WRITE)) { - Assertions.assertEquals(1, ch.size()); + assertEquals(1, ch.size()); } } From 6901c32e69775d212dae4e0c0144bef4215797a8 Mon Sep 17 00:00:00 2001 From: JaniruTEC <52893617+JaniruTEC@users.noreply.github.com> Date: Tue, 8 Aug 2023 13:05:02 +0200 Subject: [PATCH 04/11] Extracted InMemory-Tests to own class --- ...SystemProviderInMemoryIntegrationTest.java | 88 +++++++++++++++++++ ...yptoFileSystemProviderIntegrationTest.java | 62 ------------- 2 files changed, 88 insertions(+), 62 deletions(-) create mode 100644 src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java new file mode 100644 index 00000000..c2822284 --- /dev/null +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java @@ -0,0 +1,88 @@ +package org.cryptomator.cryptofs; + +import com.google.common.jimfs.Configuration; +import com.google.common.jimfs.Jimfs; +import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.api.MasterkeyLoader; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Set; + +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; +import static org.cryptomator.cryptofs.CryptoFileSystemProperties.cryptoFileSystemProperties; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class CryptoFileSystemProviderInMemoryIntegrationTest { + + private static FileSystem tmpFs; + private static Path pathToVault; + + @BeforeAll + public static void beforeAll() { + tmpFs = Jimfs.newFileSystem(Configuration.unix()); + pathToVault = tmpFs.getPath("/vault"); + } + + @BeforeEach + public void beforeEach() throws IOException { + Files.createDirectory(pathToVault); + } + + @AfterEach + public void afterEach() throws IOException { + try (var paths = Files.walk(pathToVault)) { + var nodes = paths.sorted(Comparator.reverseOrder()).toList(); + for (var node : nodes) { + Files.delete(node); + } + } + } + + @AfterAll + public static void afterAll() throws IOException { + tmpFs.close(); + } + + @Test + @DisplayName("Replace an existing, shortened file") + public void testReplaceExistingShortenedFile() throws IOException { + try (var fs = setupCryptoFs(50, 100, false)) { + var fiftyCharName2 = "/50char2_50char2_50char2_50char2_50char2_50char.txt"; //since filename encryption increases filename length, 50 cleartext chars are sufficient + var source = fs.getPath("/source.txt"); + var target = fs.getPath(fiftyCharName2); + Files.createFile(source); + Files.createFile(target); + + assertDoesNotThrow(() -> Files.move(source, target, REPLACE_EXISTING)); + assertTrue(Files.notExists(source)); + assertTrue(Files.exists(target)); + } + } + + private FileSystem setupCryptoFs(int ciphertextShorteningThreshold, int maxCleartextFilename, boolean readonly) throws IOException { + byte[] key = new byte[64]; + Arrays.fill(key, (byte) 0x55); + var keyLoader = Mockito.mock(MasterkeyLoader.class); + Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(key)); + var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).withShorteningThreshold(ciphertextShorteningThreshold).withMaxCleartextNameLength(maxCleartextFilename).withFlags(readonly ? Set.of(CryptoFileSystemProperties.FileSystemFlags.READONLY) : Set.of()).build(); + CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); + URI fsUri = CryptoFileSystemUri.create(pathToVault); + return FileSystems.newFileSystem(fsUri, cryptoFileSystemProperties().withKeyLoader(keyLoader).build()); + } + +} diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java index 2f6e1b5b..952a79f6 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java @@ -589,68 +589,6 @@ public void testMoveFileFromOneCryptoFileSystemToAnother() throws IOException { } - - @Nested - public class InMemory { - - private static FileSystem tmpFs; - private static Path pathToVault; - - @BeforeAll - public static void beforeAll() { - tmpFs = Jimfs.newFileSystem(Configuration.unix()); - pathToVault = tmpFs.getPath("/vault"); - } - - @BeforeEach - public void beforeEach() throws IOException { - Files.createDirectory(pathToVault); - } - - @AfterEach - public void afterEach() throws IOException { - try (var paths = Files.walk(pathToVault)) { - var nodes = paths.sorted(Comparator.reverseOrder()).toList(); - for (var node : nodes) { - Files.delete(node); - } - } - } - - @AfterAll - public static void afterAll() throws IOException { - tmpFs.close(); - } - - @Test - @DisplayName("Replace an existing, shortened file") - public void testReplaceExistingShortenedFile() throws IOException { - try (var fs = setupCryptoFs(50, 100, false)) { - var fiftyCharName2 = "/50char2_50char2_50char2_50char2_50char2_50char.txt"; //since filename encryption increases filename length, 50 cleartext chars are sufficient - var source = fs.getPath("/source.txt"); - var target = fs.getPath(fiftyCharName2); - Files.createFile(source); - Files.createFile(target); - - assertDoesNotThrow(() -> Files.move(source, target, REPLACE_EXISTING)); - assertTrue(Files.notExists(source)); - assertTrue(Files.exists(target)); - } - } - - private FileSystem setupCryptoFs(int ciphertextShorteningThreshold, int maxCleartextFilename, boolean readonly) throws IOException { - byte[] key = new byte[64]; - Arrays.fill(key, (byte) 0x55); - var keyLoader = Mockito.mock(MasterkeyLoader.class); - Mockito.when(keyLoader.loadKey(Mockito.any())).thenAnswer(ignored -> new Masterkey(key)); - var properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withKeyLoader(keyLoader).withShorteningThreshold(ciphertextShorteningThreshold).withMaxCleartextNameLength(maxCleartextFilename).withFlags(readonly ? Set.of(CryptoFileSystemProperties.FileSystemFlags.READONLY) : Set.of()).build(); - CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key")); - URI fsUri = CryptoFileSystemUri.create(pathToVault); - return FileSystems.newFileSystem(fsUri, cryptoFileSystemProperties().withKeyLoader(keyLoader).build()); - } - - } - @Nested @EnabledOnOs({OS.MAC, OS.LINUX}) @TestInstance(TestInstance.Lifecycle.PER_CLASS) From 28566e50d88c775a920b331148109c891eaa1ac0 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 9 Aug 2023 12:24:02 +0200 Subject: [PATCH 05/11] rename variable --- .../cryptomator/cryptofs/CryptoFileSystemImpl.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java index 8bd83d2d..8a69c831 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java @@ -621,18 +621,18 @@ private void moveDirectory(CryptoPath cleartextSource, CryptoPath cleartextTarge throw new AtomicMoveNotSupportedException(cleartextSource.toString(), cleartextTarget.toString(), "Replacing directories during move requires non-atomic status checks."); } // check if dir is empty: - Path oldCiphertextDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).path; - boolean oldCiphertextDirExists = true; - try (DirectoryStream ds = Files.newDirectoryStream(oldCiphertextDir)) { + Path targetCiphertextDirContentDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).path; + boolean targetCiphertextDirExists = true; + try (DirectoryStream ds = Files.newDirectoryStream(targetCiphertextDirContentDir)) { if (ds.iterator().hasNext()) { throw new DirectoryNotEmptyException(cleartextTarget.toString()); } } catch (NoSuchFileException e) { - oldCiphertextDirExists = false; + targetCiphertextDirExists = false; } // cleanup dir to be replaced: - if (oldCiphertextDirExists) { - Files.walkFileTree(oldCiphertextDir, DeletingFileVisitor.INSTANCE); + if (targetCiphertextDirExists) { + Files.walkFileTree(targetCiphertextDirContentDir, DeletingFileVisitor.INSTANCE); } Files.walkFileTree(ciphertextTarget.getRawPath(), DeletingFileVisitor.INSTANCE); } From 9ed893e1a8d7a245c0c77b70c1e0da32c6e38f7c Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 9 Aug 2023 12:25:00 +0200 Subject: [PATCH 06/11] first orphanize, then remove content --- .../java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java index 8a69c831..284739d3 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java @@ -630,11 +630,12 @@ private void moveDirectory(CryptoPath cleartextSource, CryptoPath cleartextTarge } catch (NoSuchFileException e) { targetCiphertextDirExists = false; } - // cleanup dir to be replaced: + //delete dir link + Files.walkFileTree(ciphertextTarget.getRawPath(), DeletingFileVisitor.INSTANCE); + // cleanup content dir if (targetCiphertextDirExists) { Files.walkFileTree(targetCiphertextDirContentDir, DeletingFileVisitor.INSTANCE); } - Files.walkFileTree(ciphertextTarget.getRawPath(), DeletingFileVisitor.INSTANCE); } // no exceptions until this point, so MOVE: From 7790221c0725b5e8b27554143d801325d836a721 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 9 Aug 2023 12:58:03 +0200 Subject: [PATCH 07/11] Fixes #176 --- .../cryptofs/CryptoFileSystemImpl.java | 3 ++- .../cryptofs/dir/DirIdBackupFilter.java | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/cryptomator/cryptofs/dir/DirIdBackupFilter.java diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java index 284739d3..4852db13 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java @@ -18,6 +18,7 @@ import org.cryptomator.cryptofs.common.DeletingFileVisitor; import org.cryptomator.cryptofs.common.FinallyUtil; import org.cryptomator.cryptofs.dir.CiphertextDirectoryDeleter; +import org.cryptomator.cryptofs.dir.DirIdBackupFilter; import org.cryptomator.cryptofs.dir.DirectoryStreamFactory; import org.cryptomator.cryptofs.fh.OpenCryptoFiles; import org.cryptomator.cryptolib.api.Cryptor; @@ -623,7 +624,7 @@ private void moveDirectory(CryptoPath cleartextSource, CryptoPath cleartextTarge // check if dir is empty: Path targetCiphertextDirContentDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).path; boolean targetCiphertextDirExists = true; - try (DirectoryStream ds = Files.newDirectoryStream(targetCiphertextDirContentDir)) { + try (DirectoryStream ds = Files.newDirectoryStream(targetCiphertextDirContentDir, new DirIdBackupFilter())) { if (ds.iterator().hasNext()) { throw new DirectoryNotEmptyException(cleartextTarget.toString()); } diff --git a/src/main/java/org/cryptomator/cryptofs/dir/DirIdBackupFilter.java b/src/main/java/org/cryptomator/cryptofs/dir/DirIdBackupFilter.java new file mode 100644 index 00000000..5c2806b4 --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/dir/DirIdBackupFilter.java @@ -0,0 +1,18 @@ +package org.cryptomator.cryptofs.dir; + +import org.cryptomator.cryptofs.common.Constants; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Path; + +public class DirIdBackupFilter implements DirectoryStream.Filter { + + private final Path skippedEntry = Path.of(Constants.DIR_BACKUP_FILE_NAME); + + @Override + public boolean accept(Path entry) throws IOException { + return entry.getFileName().equals(skippedEntry); + } + +} From 139f64165c87ecd83fa174c51bb26d529cb09fca Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 9 Aug 2023 12:58:26 +0200 Subject: [PATCH 08/11] add test --- ...FileSystemProviderInMemoryIntegrationTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java index c2822284..6b494ebb 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderInMemoryIntegrationTest.java @@ -58,6 +58,21 @@ public static void afterAll() throws IOException { tmpFs.close(); } + @Test + @DisplayName("Replace an existing, shortened, empty directory") + public void testReplaceExistingShortenedDirEmpty() throws IOException { + try (var fs = setupCryptoFs(50, 100, false)) { + var dirName50Chars = "/target_89_123456789_123456789_123456789_123456789_"; //since filename encryption increases filename length, 50 cleartext chars are sufficient + var source = fs.getPath("/sourceDir"); + var target = fs.getPath(dirName50Chars); + Files.createDirectory(source); + Files.createDirectory(target); + assertDoesNotThrow(() -> Files.move(source, target, REPLACE_EXISTING)); + assertTrue(Files.notExists(source)); + assertTrue(Files.exists(target)); + } + } + @Test @DisplayName("Replace an existing, shortened file") public void testReplaceExistingShortenedFile() throws IOException { From 450c4b5a93aaa73a5af1fac18b051594f1af53ce Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 9 Aug 2023 13:10:58 +0200 Subject: [PATCH 09/11] fixed wrong implementation --- .../java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java | 4 ++-- ...DirIdBackupFilter.java => ExcludeDirIdBackupFilter.java} | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) rename src/main/java/org/cryptomator/cryptofs/dir/{DirIdBackupFilter.java => ExcludeDirIdBackupFilter.java} (56%) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java index 4852db13..76c67410 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java @@ -18,7 +18,7 @@ import org.cryptomator.cryptofs.common.DeletingFileVisitor; import org.cryptomator.cryptofs.common.FinallyUtil; import org.cryptomator.cryptofs.dir.CiphertextDirectoryDeleter; -import org.cryptomator.cryptofs.dir.DirIdBackupFilter; +import org.cryptomator.cryptofs.dir.ExcludeDirIdBackupFilter; import org.cryptomator.cryptofs.dir.DirectoryStreamFactory; import org.cryptomator.cryptofs.fh.OpenCryptoFiles; import org.cryptomator.cryptolib.api.Cryptor; @@ -624,7 +624,7 @@ private void moveDirectory(CryptoPath cleartextSource, CryptoPath cleartextTarge // check if dir is empty: Path targetCiphertextDirContentDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).path; boolean targetCiphertextDirExists = true; - try (DirectoryStream ds = Files.newDirectoryStream(targetCiphertextDirContentDir, new DirIdBackupFilter())) { + try (DirectoryStream ds = Files.newDirectoryStream(targetCiphertextDirContentDir, new ExcludeDirIdBackupFilter())) { if (ds.iterator().hasNext()) { throw new DirectoryNotEmptyException(cleartextTarget.toString()); } diff --git a/src/main/java/org/cryptomator/cryptofs/dir/DirIdBackupFilter.java b/src/main/java/org/cryptomator/cryptofs/dir/ExcludeDirIdBackupFilter.java similarity index 56% rename from src/main/java/org/cryptomator/cryptofs/dir/DirIdBackupFilter.java rename to src/main/java/org/cryptomator/cryptofs/dir/ExcludeDirIdBackupFilter.java index 5c2806b4..328a2922 100644 --- a/src/main/java/org/cryptomator/cryptofs/dir/DirIdBackupFilter.java +++ b/src/main/java/org/cryptomator/cryptofs/dir/ExcludeDirIdBackupFilter.java @@ -6,13 +6,11 @@ import java.nio.file.DirectoryStream; import java.nio.file.Path; -public class DirIdBackupFilter implements DirectoryStream.Filter { - - private final Path skippedEntry = Path.of(Constants.DIR_BACKUP_FILE_NAME); +public class ExcludeDirIdBackupFilter implements DirectoryStream.Filter { @Override public boolean accept(Path entry) throws IOException { - return entry.getFileName().equals(skippedEntry); + return !entry.equals(entry.resolveSibling(Constants.DIR_BACKUP_FILE_NAME)); } } From d653636379b166eea02264eb7eeb765b3ae521ff Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 9 Aug 2023 16:01:22 +0200 Subject: [PATCH 10/11] Apply suggestions from code review Co-authored-by: Sebastian Stenzel --- .../cryptofs/CryptoFileSystemImpl.java | 4 ++-- .../cryptofs/dir/DirectoryStreamFilters.java | 12 ++++++++++++ .../cryptofs/dir/ExcludeDirIdBackupFilter.java | 16 ---------------- 3 files changed, 14 insertions(+), 18 deletions(-) create mode 100644 src/main/java/org/cryptomator/cryptofs/dir/DirectoryStreamFilters.java delete mode 100644 src/main/java/org/cryptomator/cryptofs/dir/ExcludeDirIdBackupFilter.java diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java index 76c67410..d3d7af7c 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java @@ -18,7 +18,7 @@ import org.cryptomator.cryptofs.common.DeletingFileVisitor; import org.cryptomator.cryptofs.common.FinallyUtil; import org.cryptomator.cryptofs.dir.CiphertextDirectoryDeleter; -import org.cryptomator.cryptofs.dir.ExcludeDirIdBackupFilter; +import org.cryptomator.cryptofs.dir.DirectoryStreamFilters; import org.cryptomator.cryptofs.dir.DirectoryStreamFactory; import org.cryptomator.cryptofs.fh.OpenCryptoFiles; import org.cryptomator.cryptolib.api.Cryptor; @@ -624,7 +624,7 @@ private void moveDirectory(CryptoPath cleartextSource, CryptoPath cleartextTarge // check if dir is empty: Path targetCiphertextDirContentDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).path; boolean targetCiphertextDirExists = true; - try (DirectoryStream ds = Files.newDirectoryStream(targetCiphertextDirContentDir, new ExcludeDirIdBackupFilter())) { + try (DirectoryStream ds = Files.newDirectoryStream(targetCiphertextDirContentDir, DirectoryStreamFilters.EXCLUDE_DIR_ID_BACKUP)) { if (ds.iterator().hasNext()) { throw new DirectoryNotEmptyException(cleartextTarget.toString()); } diff --git a/src/main/java/org/cryptomator/cryptofs/dir/DirectoryStreamFilters.java b/src/main/java/org/cryptomator/cryptofs/dir/DirectoryStreamFilters.java new file mode 100644 index 00000000..13f87c11 --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/dir/DirectoryStreamFilters.java @@ -0,0 +1,12 @@ +package org.cryptomator.cryptofs.dir; + +import org.cryptomator.cryptofs.common.Constants; + +import java.nio.file.DirectoryStream; +import java.nio.file.Path; + +public interface DirectoryStreamFilters { + + static DirectoryStream.Filter EXCLUDE_DIR_ID_BACKUP = p -> !p.equals(p.resolveSibling(Constants.DIR_BACKUP_FILE_NAME)); + +} diff --git a/src/main/java/org/cryptomator/cryptofs/dir/ExcludeDirIdBackupFilter.java b/src/main/java/org/cryptomator/cryptofs/dir/ExcludeDirIdBackupFilter.java deleted file mode 100644 index 328a2922..00000000 --- a/src/main/java/org/cryptomator/cryptofs/dir/ExcludeDirIdBackupFilter.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.cryptomator.cryptofs.dir; - -import org.cryptomator.cryptofs.common.Constants; - -import java.io.IOException; -import java.nio.file.DirectoryStream; -import java.nio.file.Path; - -public class ExcludeDirIdBackupFilter implements DirectoryStream.Filter { - - @Override - public boolean accept(Path entry) throws IOException { - return !entry.equals(entry.resolveSibling(Constants.DIR_BACKUP_FILE_NAME)); - } - -} From 322603b53a55f0562465af5fa8ff6ca44ae4fb5a Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 9 Aug 2023 17:12:51 +0200 Subject: [PATCH 11/11] prepare 2.6.7 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0a797544..831af0b3 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.cryptomator cryptofs - 2.7.0-SNAPSHOT + 2.6.7 Cryptomator Crypto Filesystem This library provides the Java filesystem provider used by Cryptomator. https://github.com/cryptomator/cryptofs