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 {