diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index d26ab7d2..da527ddf 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -14,7 +14,7 @@ jobs:
show-progress: false
- uses: actions/setup-java@v4
with:
- java-version: 17
+ java-version: 21
distribution: 'temurin'
cache: 'maven'
- name: Cache SonarCloud packages
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 237bcac3..9bfa8088 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -22,7 +22,7 @@ jobs:
show-progress: false
- uses: actions/setup-java@v4
with:
- java-version: 17
+ java-version: 21
distribution: 'temurin'
cache: 'maven'
- name: Initialize CodeQL
diff --git a/.github/workflows/dependency-check.yml b/.github/workflows/dependency-check.yml
index 4e7e7d0f..601975f4 100644
--- a/.github/workflows/dependency-check.yml
+++ b/.github/workflows/dependency-check.yml
@@ -14,7 +14,7 @@ jobs:
with:
runner-os: 'ubuntu-latest'
java-distribution: 'temurin'
- java-version: 17
+ java-version: 21
secrets:
nvd-api-key: ${{ secrets.NVD_API_KEY }}
slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
diff --git a/.github/workflows/publish-central.yml b/.github/workflows/publish-central.yml
index 4129b18c..07727e4c 100644
--- a/.github/workflows/publish-central.yml
+++ b/.github/workflows/publish-central.yml
@@ -16,7 +16,7 @@ jobs:
show-progress: false
- uses: actions/setup-java@v4
with:
- java-version: 17
+ java-version: 21
distribution: 'temurin'
cache: 'maven'
server-id: ossrh # Value of the distributionManagement/repository/id field of the pom.xml
diff --git a/.github/workflows/publish-github.yml b/.github/workflows/publish-github.yml
index dff71ad5..59b312e4 100644
--- a/.github/workflows/publish-github.yml
+++ b/.github/workflows/publish-github.yml
@@ -12,7 +12,7 @@ jobs:
show-progress: false
- uses: actions/setup-java@v4
with:
- java-version: 17
+ java-version: 21
distribution: 'temurin'
cache: 'maven'
- name: Enforce project version ${{ github.event.release.tag_name }}
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 67e1e611..9dc782bb 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -8,5 +8,5 @@
-
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 6ed04e88..3050a0db 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,7 @@
[![Known Vulnerabilities](https://snyk.io/test/github/cryptomator/cryptofs/badge.svg)](https://snyk.io/test/github/cryptomator/cryptofs)
**CryptoFS:** Implementation of the [Cryptomator](https://github.com/cryptomator/cryptomator) encryption scheme.
+For more info about the encryption scheme, read the [docs](https://docs.cryptomator.org/en/latest/security/vault/).
## Features
@@ -98,7 +99,7 @@ For more details on how to use the constructed `FileSystem`, you may consult the
### Dependencies
-* Java 17
+* Java 21
* Maven 3
### Run Maven
diff --git a/pom.xml b/pom.xml
index 3847f6b0..569edd68 100644
--- a/pom.xml
+++ b/pom.xml
@@ -15,7 +15,7 @@
UTF-8
- 17
+ 21
2.2.0
@@ -27,7 +27,7 @@
5.10.3
- 5.2.0
+ 5.12.0
3.0
1.3.0
@@ -114,7 +114,7 @@
org.mockito
- mockito-inline
+ mockito-core
${mockito.version}
test
diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java
index e694130f..5095b5cd 100644
--- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java
@@ -72,7 +72,6 @@
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
@@ -959,6 +958,7 @@ public void copyDirectory() throws IOException {
when(physicalFsProv.newFileChannel(Mockito.eq(ciphertextDestinationDirFile), Mockito.any(), any(FileAttribute[].class))).thenReturn(ciphertextTargetDirDirFileFileChannel);
when(cryptoPathMapper.getCiphertextFileType(cleartextSource)).thenReturn(CiphertextFileType.DIRECTORY);
when(cryptoPathMapper.getCiphertextFileType(cleartextDestination)).thenThrow(NoSuchFileException.class);
+ when(physicalFsProv.exists(ciphertextTargetParent)).thenReturn(true);
Mockito.doThrow(new NoSuchFileException("ciphertextDestinationDirFile")).when(physicalFsProv).checkAccess(ciphertextDestinationFile);
inTest.copy(cleartextSource, cleartextDestination);
@@ -1007,6 +1007,7 @@ public void moveDirectoryCopyBasicAttributes() throws IOException {
when(physicalFsProv.readAttributes(Mockito.same(ciphertextSourceDir), Mockito.same(BasicFileAttributes.class), any(LinkOption[].class))).thenReturn(srcAttrs);
when(physicalFsProv.getFileAttributeView(Mockito.same(ciphertextDestinationDir), Mockito.same(BasicFileAttributeView.class), any(LinkOption[].class))).thenReturn(dstAttrView);
when(physicalFsProv.newFileChannel(Mockito.same(ciphertextDestinationDirFile), Mockito.anySet(), any(FileAttribute[].class))).thenReturn(ciphertextTargetDirDirFileFileChannel);
+ when(physicalFsProv.exists(ciphertextTargetParent)).thenReturn(true);
inTest.copy(cleartextSource, cleartextDestination, StandardCopyOption.COPY_ATTRIBUTES);
@@ -1027,6 +1028,7 @@ public void moveDirectoryCopyFileOwnerAttributes() throws IOException {
when(physicalFsProv.getFileAttributeView(Mockito.same(ciphertextSourceDir), Mockito.same(FileOwnerAttributeView.class), any(LinkOption[].class))).thenReturn(srcAttrsView);
when(physicalFsProv.getFileAttributeView(Mockito.same(ciphertextDestinationDir), Mockito.same(FileOwnerAttributeView.class), any(LinkOption[].class))).thenReturn(dstAttrView);
when(physicalFsProv.newFileChannel(Mockito.same(ciphertextDestinationDirFile), Mockito.anySet(), any(FileAttribute[].class))).thenReturn(ciphertextTargetDirDirFileFileChannel);
+ when(physicalFsProv.exists(ciphertextTargetParent)).thenReturn(true);
inTest.copy(cleartextSource, cleartextDestination, StandardCopyOption.COPY_ATTRIBUTES);
@@ -1050,6 +1052,7 @@ public void moveDirectoryCopyPosixAttributes() throws IOException {
when(physicalFsProv.readAttributes(Mockito.same(ciphertextSourceDir), Mockito.same(PosixFileAttributes.class), any(LinkOption[].class))).thenReturn(srcAttrs);
when(physicalFsProv.getFileAttributeView(Mockito.same(ciphertextDestinationDir), Mockito.same(PosixFileAttributeView.class), any(LinkOption[].class))).thenReturn(dstAttrView);
when(physicalFsProv.newFileChannel(Mockito.same(ciphertextDestinationDirFile), Mockito.anySet(), any(FileAttribute[].class))).thenReturn(ciphertextTargetDirDirFileFileChannel);
+ when(physicalFsProv.exists(ciphertextTargetParent)).thenReturn(true);
inTest.copy(cleartextSource, cleartextDestination, StandardCopyOption.COPY_ATTRIBUTES);
@@ -1073,6 +1076,7 @@ public void moveDirectoryCopyDosAttributes() throws IOException {
when(physicalFsProv.readAttributes(Mockito.same(ciphertextSourceDir), Mockito.same(DosFileAttributes.class), any(LinkOption[].class))).thenReturn(srcAttrs);
when(physicalFsProv.getFileAttributeView(Mockito.same(ciphertextDestinationDir), Mockito.same(DosFileAttributeView.class), any(LinkOption[].class))).thenReturn(dstAttrView);
when(physicalFsProv.newFileChannel(Mockito.same(ciphertextDestinationDirFile), Mockito.anySet(), any(FileAttribute[].class))).thenReturn(ciphertextTargetDirDirFileFileChannel);
+ when(physicalFsProv.exists(ciphertextTargetParent)).thenReturn(true);
inTest.copy(cleartextSource, cleartextDestination, StandardCopyOption.COPY_ATTRIBUTES);
@@ -1168,6 +1172,7 @@ public void createDirectoryIfPathCiphertextFileDoesExistThrowsFileAlreadyExcepti
when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CiphertextDirectory("foo", ciphertextParent));
when(ciphertextParent.getFileSystem()).thenReturn(fileSystem);
doThrow(new FileAlreadyExistsException(path.toString())).when(cryptoPathMapper).assertNonExisting(path);
+ when(provider.exists(ciphertextParent)).thenReturn(true);
FileAlreadyExistsException e = Assertions.assertThrows(FileAlreadyExistsException.class, () -> {
inTest.createDirectory(path);
@@ -1177,7 +1182,7 @@ public void createDirectoryIfPathCiphertextFileDoesExistThrowsFileAlreadyExcepti
@Test
public void createDirectoryCreatesDirectoryIfConditonsAreMet() throws IOException {
- Path ciphertextParent = mock(Path.class, "ciphertextParent");
+ Path ciphertextParent = mock(Path.class, "d/00/00");
Path ciphertextRawPath = mock(Path.class, "d/00/00/path.c9r");
Path ciphertextDirFile = mock(Path.class, "d/00/00/path.c9r/dir.c9r");
Path ciphertextDirPath = mock(Path.class, "d/FF/FF/");
@@ -1187,7 +1192,7 @@ public void createDirectoryCreatesDirectoryIfConditonsAreMet() throws IOExceptio
when(ciphertextRawPath.resolve("dir.c9r")).thenReturn(ciphertextDirFile);
when(cryptoPathMapper.getCiphertextFilePath(path)).thenReturn(ciphertextPath);
when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CiphertextDirectory(dirId, ciphertextDirPath));
- when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CiphertextDirectory("parentDirId", ciphertextDirPath));
+ when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CiphertextDirectory("parentDirId", ciphertextParent));
when(cryptoPathMapper.getCiphertextFileType(path)).thenThrow(NoSuchFileException.class);
when(ciphertextPath.getRawPath()).thenReturn(ciphertextRawPath);
when(ciphertextPath.getDirFilePath()).thenReturn(ciphertextDirFile);
@@ -1197,6 +1202,7 @@ public void createDirectoryCreatesDirectoryIfConditonsAreMet() throws IOExceptio
when(ciphertextDirPath.getFileSystem()).thenReturn(fileSystem);
when(ciphertextDirFile.getName(3)).thenReturn(mock(Path.class, "path.c9r"));
when(provider.newFileChannel(ciphertextDirFile, EnumSet.of(StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE))).thenReturn(channel);
+ when(provider.exists(ciphertextParent)).thenReturn(true);
inTest.createDirectory(path);
@@ -1206,7 +1212,7 @@ public void createDirectoryCreatesDirectoryIfConditonsAreMet() throws IOExceptio
@Test
public void createDirectoryClearsDirIdAndDeletesDirFileIfCreatingDirFails() throws IOException {
- Path ciphertextParent = mock(Path.class, "ciphertextParent");
+ Path ciphertextParent = mock(Path.class, "d/00/00");
Path ciphertextRawPath = mock(Path.class, "d/00/00/path.c9r");
Path ciphertextDirFile = mock(Path.class, "d/00/00/path.c9r/dir.c9r");
Path ciphertextDirPath = mock(Path.class, "d/FF/FF/");
@@ -1216,7 +1222,7 @@ public void createDirectoryClearsDirIdAndDeletesDirFileIfCreatingDirFails() thro
when(ciphertextRawPath.resolve("dir.c9r")).thenReturn(ciphertextDirFile);
when(cryptoPathMapper.getCiphertextFilePath(path)).thenReturn(ciphertextPath);
when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CiphertextDirectory(dirId, ciphertextDirPath));
- when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CiphertextDirectory("parentDirId", ciphertextDirPath));
+ when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CiphertextDirectory("parentDirId", ciphertextParent));
when(cryptoPathMapper.getCiphertextFileType(path)).thenThrow(NoSuchFileException.class);
when(ciphertextPath.getRawPath()).thenReturn(ciphertextRawPath);
when(ciphertextPath.getDirFilePath()).thenReturn(ciphertextDirFile);
@@ -1226,15 +1232,18 @@ public void createDirectoryClearsDirIdAndDeletesDirFileIfCreatingDirFails() thro
when(ciphertextDirPath.getFileSystem()).thenReturn(fileSystem);
when(ciphertextDirFile.getName(3)).thenReturn(mock(Path.class, "path.c9r"));
when(provider.newFileChannel(ciphertextDirFile, EnumSet.of(StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE))).thenReturn(channel);
+ when(provider.exists(ciphertextParent)).thenReturn(true);
- // make createDirectory with an FileSystemException during Files.createDirectories(ciphertextDirPath)
- doThrow(new IOException()).when(provider).createDirectory(ciphertextDirPath);
+ // make createDirectory with an FileSystemException during Files.createDirectories(ciphertextContentDir)
+ doThrow(new IOException()).when(provider).readAttributesIfExists(ciphertextDirPath, BasicFileAttributes.class);
+ doThrow(new FileAlreadyExistsException("very specific")).when(provider).createDirectory(ciphertextDirPath);
when(ciphertextDirPath.toAbsolutePath()).thenReturn(ciphertextDirPath);
when(ciphertextDirPath.getParent()).thenReturn(null);
- Assertions.assertThrows(IOException.class, () -> {
+ var exception = Assertions.assertThrows(FileAlreadyExistsException.class, () -> {
inTest.createDirectory(path);
});
+ Assertions.assertEquals("very specific", exception.getMessage());
verify(readonlyFlag).assertWritable();
verify(provider).delete(ciphertextDirFile);
verify(dirIdProvider).delete(ciphertextDirFile);
@@ -1243,7 +1252,7 @@ public void createDirectoryClearsDirIdAndDeletesDirFileIfCreatingDirFails() thro
@Test
public void createDirectoryBackupsDirIdInCiphertextDirPath() throws IOException {
- Path ciphertextParent = mock(Path.class, "ciphertextParent");
+ Path ciphertextParent = mock(Path.class, "d/00/00");
Path ciphertextRawPath = mock(Path.class, "d/00/00/path.c9r");
Path ciphertextDirFile = mock(Path.class, "d/00/00/path.c9r/dir.c9r");
Path ciphertextDirPath = mock(Path.class, "d/FF/FF/");
@@ -1254,7 +1263,7 @@ public void createDirectoryBackupsDirIdInCiphertextDirPath() throws IOException
when(ciphertextRawPath.resolve("dir.c9r")).thenReturn(ciphertextDirFile);
when(cryptoPathMapper.getCiphertextFilePath(path)).thenReturn(ciphertextPath);
when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(cipherDirObject);
- when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CiphertextDirectory("parentDirId", ciphertextDirPath));
+ when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CiphertextDirectory("parentDirId", ciphertextParent));
when(cryptoPathMapper.getCiphertextFileType(path)).thenThrow(NoSuchFileException.class);
when(ciphertextPath.getRawPath()).thenReturn(ciphertextRawPath);
when(ciphertextPath.getDirFilePath()).thenReturn(ciphertextDirFile);
@@ -1264,6 +1273,7 @@ public void createDirectoryBackupsDirIdInCiphertextDirPath() throws IOException
when(ciphertextDirPath.getFileSystem()).thenReturn(fileSystem);
when(ciphertextDirFile.getName(3)).thenReturn(mock(Path.class, "path.c9r"));
when(provider.newFileChannel(ciphertextDirFile, EnumSet.of(StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE))).thenReturn(channel);
+ when(provider.exists(ciphertextParent)).thenReturn(true);
inTest.createDirectory(path);
diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java
index e2da36a8..32241fb8 100644
--- a/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java
@@ -290,6 +290,7 @@ public void testGetCiphertextFileTypeForDirectory() throws IOException {
Mockito.when(underlyingFileSystemProvider.readAttributes(dirFilePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenReturn(Mockito.mock(BasicFileAttributes.class));
Mockito.when(underlyingFileSystemProvider.readAttributes(symlinkFilePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenThrow(NoSuchFileException.class);
Mockito.when(underlyingFileSystemProvider.readAttributes(contentsFilePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenThrow(NoSuchFileException.class);
+ Mockito.when(underlyingFileSystemProvider.exists(dirFilePath, LinkOption.NOFOLLOW_LINKS)).thenReturn(true);
CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, vaultConfig);
@@ -305,6 +306,7 @@ public void testGetCiphertextFileTypeForSymlink() throws IOException {
Mockito.when(underlyingFileSystemProvider.readAttributes(dirFilePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenThrow(NoSuchFileException.class);
Mockito.when(underlyingFileSystemProvider.readAttributes(symlinkFilePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenReturn(Mockito.mock(BasicFileAttributes.class));
Mockito.when(underlyingFileSystemProvider.readAttributes(contentsFilePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenThrow(NoSuchFileException.class);
+ Mockito.when(underlyingFileSystemProvider.exists(symlinkFilePath, LinkOption.NOFOLLOW_LINKS)).thenReturn(true);
CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, vaultConfig);
@@ -322,6 +324,7 @@ public void testGetCiphertextFileTypeForShortenedFile() throws IOException {
Mockito.when(underlyingFileSystemProvider.readAttributes(contentsFilePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenReturn(Mockito.mock(BasicFileAttributes.class));
Mockito.when(fileNameCryptor.encryptFilename(Mockito.any(), Mockito.eq("LONGCLEAR"), Mockito.any())).thenReturn(Strings.repeat("A", 1000));
Mockito.when(longFileNameProvider.deflate(Mockito.any())).thenReturn(new LongFileNameProvider.DeflatedFileName(c9rPath, null, null));
+ Mockito.when(underlyingFileSystemProvider.exists(contentsFilePath, LinkOption.NOFOLLOW_LINKS)).thenReturn(true);
CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, vaultConfig);