diff --git a/pom.xml b/pom.xml index 9e205f51..2fa7fcdd 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.cryptomator cryptofs - 2.5.3 + 2.6.0 Cryptomator Crypto Filesystem This library provides the Java filesystem provider used by Cryptomator. https://github.com/cryptomator/cryptofs diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystem.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystem.java index 3a344d25..7f82e2d6 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystem.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystem.java @@ -1,5 +1,6 @@ package org.cryptomator.cryptofs; +import java.io.IOException; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; @@ -29,6 +30,17 @@ public abstract class CryptoFileSystem extends FileSystem { */ public abstract Path getPathToVault(); + /** + * Provides the {@link Path} to the (data) ciphertext from a given cleartext path. + * + * @param cleartextPath absolute path to the cleartext file or folder belonging to this {@link CryptoFileSystem}. Internally the path must be an instance of {@link CryptoPath} + * @return the {@link Path} to ciphertext file or folder containing teh actual encrypted data + * @throws java.nio.file.ProviderMismatchException if the cleartext path does not belong to this CryptoFileSystem + * @throws java.nio.file.NoSuchFileException if for the cleartext path no ciphertext resource exists + * @throws IOException if an I/O error occurs looking for the ciphertext resource + */ + public abstract Path getCiphertextPath(Path cleartextPath) throws IOException; + /** * Provides file system performance statistics. * diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java index f4523c67..35cb3359 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java @@ -136,6 +136,20 @@ public Path getPathToVault() { return pathToVault; } + @Override + public Path getCiphertextPath(Path cleartextPath) throws IOException { + var p = CryptoPath.castAndAssertAbsolute(cleartextPath); + var nodeType = cryptoPathMapper.getCiphertextFileType(p); + var cipherFile = cryptoPathMapper.getCiphertextFilePath(p); + if( nodeType == CiphertextFileType.DIRECTORY) { + return cryptoPathMapper.getCiphertextDir(p).path; + } else if( nodeType == CiphertextFileType.SYMLINK) { + return cipherFile.getSymlinkFilePath(); + } else { + return cipherFile.getFilePath(); + } + } + @Override public CryptoFileSystemStats getStats() { return stats; diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java index 5b0b1521..9a8cc039 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java @@ -42,6 +42,7 @@ import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.PathMatcher; +import java.nio.file.ProviderMismatchException; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; import java.nio.file.attribute.BasicFileAttributeView; @@ -119,11 +120,11 @@ public void setup() { when(fileSystemProperties.maxCleartextNameLength()).thenReturn(32768); - inTest = new CryptoFileSystemImpl(provider, cryptoFileSystems, pathToVault, cryptor, - fileStore, stats, cryptoPathMapper, cryptoPathFactory, - pathMatcherFactory, directoryStreamFactory, dirIdProvider, dirIdBackup, - fileAttributeProvider, fileAttributeByNameProvider, fileAttributeViewProvider, - openCryptoFiles, symlinks, finallyUtil, ciphertextDirDeleter, readonlyFlag, + inTest = new CryptoFileSystemImpl(provider, cryptoFileSystems, pathToVault, cryptor, // + fileStore, stats, cryptoPathMapper, cryptoPathFactory, // + pathMatcherFactory, directoryStreamFactory, dirIdProvider, dirIdBackup, // + fileAttributeProvider, fileAttributeByNameProvider, fileAttributeViewProvider, // + openCryptoFiles, symlinks, finallyUtil, ciphertextDirDeleter, readonlyFlag, // fileSystemProperties); } @@ -188,6 +189,89 @@ public void testGetFileStoresReturnsFileStore() { Assertions.assertSame(fileStore, inTest.getFileStore()); } + @Nested + public class PathToDataCiphertext { + + @Test + @DisplayName("Getting data ciphertext path of directory returns ciphertext content dir") + public void testCleartextDirectory() throws IOException { + Path ciphertext = Mockito.mock(Path.class, "/d/AB/CD...XYZ/"); + Path cleartext = inTest.getPath("/"); + try (var cryptoPathMock = Mockito.mockStatic(CryptoPath.class)) { + cryptoPathMock.when(() -> CryptoPath.castAndAssertAbsolute(any())).thenReturn(cleartext); + when(cryptoPathMapper.getCiphertextFileType(any())).thenReturn(CiphertextFileType.DIRECTORY); + when(cryptoPathMapper.getCiphertextDir(any())).thenReturn(new CiphertextDirectory("foo", ciphertext)); + + Path result = inTest.getCiphertextPath(cleartext); + Assertions.assertEquals(ciphertext, result); + } + } + + @Test + @DisplayName("Getting data ciphertext path of file returns ciphertext file") + public void testCleartextFile() throws IOException { + Path ciphertext = Mockito.mock(Path.class, "/d/AB/CD..XYZ/foo.c9r"); + Path cleartext = inTest.getPath("/foo.bar"); + try (var cryptoPathMock = Mockito.mockStatic(CryptoPath.class)) { + CiphertextFilePath p = Mockito.mock(CiphertextFilePath.class); + cryptoPathMock.when(() -> CryptoPath.castAndAssertAbsolute(any())).thenReturn(cleartext); + when(cryptoPathMapper.getCiphertextFileType(any())).thenReturn(CiphertextFileType.FILE); + when(cryptoPathMapper.getCiphertextFilePath(any())).thenReturn(p); + when(p.getFilePath()).thenReturn(ciphertext); + + Path result = inTest.getCiphertextPath(cleartext); + Assertions.assertEquals(ciphertext, result); + } + } + + @Test + @DisplayName("Getting data ciphertext path of symlink returns ciphertext symlink.c9r") + public void testCleartextSymlink() throws IOException { + Path ciphertext = Mockito.mock(Path.class, "/d/AB/CD..XYZ/foo.c9s/symlink.c9r"); + Path cleartext = inTest.getPath("/foo.bar"); + try (var cryptoPathMock = Mockito.mockStatic(CryptoPath.class)) { + CiphertextFilePath p = Mockito.mock(CiphertextFilePath.class); + cryptoPathMock.when(() -> CryptoPath.castAndAssertAbsolute(any())).thenReturn(cleartext); + when(cryptoPathMapper.getCiphertextFileType(any())).thenReturn(CiphertextFileType.SYMLINK); + when(cryptoPathMapper.getCiphertextFilePath(any())).thenReturn(p); + when(p.getSymlinkFilePath()).thenReturn(ciphertext); + + Path result = inTest.getCiphertextPath(cleartext); + Assertions.assertEquals(ciphertext, result); + } + } + + @Test + @DisplayName("Path not pointing into the vault throws exception") + public void testForeignPathThrows() throws IOException { + Path cleartext = Mockito.mock(Path.class, "/some.file"); + Assertions.assertThrows(ProviderMismatchException.class, () -> inTest.getCiphertextPath(cleartext)); + } + + @Test + @DisplayName("Not existing resource throws NoSuchFileException") + public void testNoSuchFile() throws IOException { + Path cleartext = inTest.getPath("/i-do-not-exist"); + try (var cryptoPathMock = Mockito.mockStatic(CryptoPath.class)) { + cryptoPathMock.when(() -> CryptoPath.castAndAssertAbsolute(any())).thenReturn(cleartext); + when(cryptoPathMapper.getCiphertextFileType(any())).thenThrow(new NoSuchFileException("no such file")); + + Assertions.assertThrows(NoSuchFileException.class, () -> inTest.getCiphertextPath(cleartext)); + } + } + + @Test + @DisplayName("Relative cleartext path throws exception") + public void testRelativePathException() throws IOException { + Path cleartext = inTest.getPath("relative/path"); + try (var cryptoPathMock = Mockito.mockStatic(CryptoPath.class)) { + cryptoPathMock.when(() -> CryptoPath.castAndAssertAbsolute(any())).thenThrow(new IllegalArgumentException()); + + Assertions.assertThrows(IllegalArgumentException.class, () -> inTest.getCiphertextPath(cleartext)); + } + } + } + @Nested public class CloseAndIsOpen {