diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java
index b0c679438..766cdd9cc 100644
--- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java
+++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java
@@ -331,7 +331,7 @@ void createDirectory(CryptoPath cleartextDir, FileAttribute>... attrs) throws
// create dir if and only if the dirFile has been created right now (not if it has been created before):
try {
Files.createDirectories(ciphertextDir.path());
- dirIdBackup.execute(ciphertextDir);
+ dirIdBackup.write(ciphertextDir);
ciphertextPath.persistLongFileName();
} catch (IOException e) {
// make sure there is no orphan dir file:
diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java
index 1360d6d73..6d4abd45f 100644
--- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java
+++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java
@@ -155,7 +155,7 @@ public static void initialize(Path pathToVault, CryptoFileSystemProperties prope
Path vaultCipherRootPath = pathToVault.resolve(Constants.DATA_DIR_NAME).resolve(dirHash.substring(0, 2)).resolve(dirHash.substring(2));
Files.createDirectories(vaultCipherRootPath);
// create dirId backup:
- DirectoryIdBackup.backupManually(cryptor, new CiphertextDirectory(Constants.ROOT_DIR_ID, vaultCipherRootPath));
+ DirectoryIdBackup.write(cryptor, new CiphertextDirectory(Constants.ROOT_DIR_ID, vaultCipherRootPath));
} finally {
Arrays.fill(rawKey, (byte) 0x00);
}
diff --git a/src/main/java/org/cryptomator/cryptofs/DirectoryIdBackup.java b/src/main/java/org/cryptomator/cryptofs/DirectoryIdBackup.java
index 6dc2f7fe5..5be4f60fb 100644
--- a/src/main/java/org/cryptomator/cryptofs/DirectoryIdBackup.java
+++ b/src/main/java/org/cryptomator/cryptofs/DirectoryIdBackup.java
@@ -1,7 +1,9 @@
package org.cryptomator.cryptofs;
import org.cryptomator.cryptofs.common.Constants;
+import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.Cryptor;
+import org.cryptomator.cryptolib.common.DecryptingReadableByteChannel;
import org.cryptomator.cryptolib.common.EncryptingWritableByteChannel;
import javax.inject.Inject;
@@ -10,15 +12,16 @@
import java.nio.channels.ByteChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
+import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
/**
- * Single purpose class to back up the directory id of an encrypted directory when it is created.
+ * Single purpose class to read or write the directory id backup of an encrypted directory.
*/
@CryptoFileSystemScoped
public class DirectoryIdBackup {
- private Cryptor cryptor;
+ private final Cryptor cryptor;
@Inject
public DirectoryIdBackup(Cryptor cryptor) {
@@ -26,15 +29,15 @@ public DirectoryIdBackup(Cryptor cryptor) {
}
/**
- * Performs the backup operation for the given {@link CiphertextDirectory} object.
+ * Writes the dirId backup file for the {@link CiphertextDirectory} object.
*
- * The directory id is written via an encrypting channel to the file {@link CiphertextDirectory#path()} /{@value Constants#DIR_BACKUP_FILE_NAME}.
+ * The directory id is written via an encrypting channel to the file {@link CiphertextDirectory#path()}.resolve({@value Constants#DIR_ID_BACKUP_FILE_NAME});
*
* @param ciphertextDirectory The cipher dir object containing the dir id and the encrypted content root
* @throws IOException if an IOException is raised during the write operation
*/
- public void execute(CiphertextDirectory ciphertextDirectory) throws IOException {
- try (var channel = Files.newByteChannel(ciphertextDirectory.path().resolve(Constants.DIR_BACKUP_FILE_NAME), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); //
+ public void write(CiphertextDirectory ciphertextDirectory) throws IOException {
+ try (var channel = Files.newByteChannel(getBackupFilePath(ciphertextDirectory.path()), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); //
var encryptingChannel = wrapEncryptionAround(channel, cryptor)) {
encryptingChannel.write(ByteBuffer.wrap(ciphertextDirectory.dirId().getBytes(StandardCharsets.US_ASCII)));
}
@@ -43,16 +46,65 @@ public void execute(CiphertextDirectory ciphertextDirectory) throws IOException
/**
* Static method to explicitly back up the directory id for a specified ciphertext directory.
*
- * @param cryptor The cryptor to be used
+ * @param cryptor The cryptor to be used for encryption
* @param ciphertextDirectory A {@link CiphertextDirectory} for which the dirId should be back up'd.
* @throws IOException when the dirId file already exists, or it cannot be written to.
*/
- public static void backupManually(Cryptor cryptor, CiphertextDirectory ciphertextDirectory) throws IOException {
- new DirectoryIdBackup(cryptor).execute(ciphertextDirectory);
+ public static void write(Cryptor cryptor, CiphertextDirectory ciphertextDirectory) throws IOException {
+ new DirectoryIdBackup(cryptor).write(ciphertextDirectory);
}
- static EncryptingWritableByteChannel wrapEncryptionAround(ByteChannel channel, Cryptor cryptor) {
+ /**
+ * Reads the dirId backup file and retrieves the directory id from it.
+ *
+ * @param ciphertextContentDir path of a ciphertext content directory
+ * @return a byte array containing the directory id
+ * @throws IOException if the dirId backup file cannot be read
+ * @throws CryptoException if the content of dirId backup file cannot be decrypted/authenticated
+ * @throws IllegalStateException if the directory id exceeds {@value Constants#MAX_DIR_ID_LENGTH} chars
+ */
+ public byte[] read(Path ciphertextContentDir) throws IOException, CryptoException, IllegalStateException {
+ var dirIdBackupFile = getBackupFilePath(ciphertextContentDir);
+ var dirIdBuffer = ByteBuffer.allocate(Constants.MAX_DIR_ID_LENGTH + 1); //a dir id contains at most 36 ascii chars, we add for security checks one more
+
+ try (var channel = Files.newByteChannel(dirIdBackupFile, StandardOpenOption.READ); //
+ var decryptingChannel = wrapDecryptionAround(channel, cryptor)) {
+ int read = decryptingChannel.read(dirIdBuffer);
+ if (read < 0 || read > Constants.MAX_DIR_ID_LENGTH) {
+ throw new IllegalStateException("Read directory id exceeds the maximum length of %d characters".formatted(Constants.MAX_DIR_ID_LENGTH));
+ }
+ }
+
+ var dirId = new byte[dirIdBuffer.position()];
+ dirIdBuffer.get(0, dirId);
+ return dirId;
+ }
+
+ /**
+ * Static method to explicitly retrieve the directory id of a ciphertext directory from the dirId backup file
+ *
+ * @param cryptor The cryptor to be used for decryption
+ * @param ciphertextContentDir path of a ciphertext content directory
+ * @return a byte array containing the directory id
+ * @throws IOException if the dirId backup file cannot be read
+ * @throws CryptoException if the content of dirId backup file cannot be decrypted/authenticated
+ * @throws IllegalStateException if the directory id exceeds {@value Constants#MAX_DIR_ID_LENGTH} chars
+ */
+ public static byte[] read(Cryptor cryptor, Path ciphertextContentDir) throws IOException, CryptoException, IllegalStateException {
+ return new DirectoryIdBackup(cryptor).read(ciphertextContentDir);
+ }
+
+
+ private static Path getBackupFilePath(Path ciphertextContentDir) {
+ return ciphertextContentDir.resolve(Constants.DIR_ID_BACKUP_FILE_NAME);
+ }
+
+ DecryptingReadableByteChannel wrapDecryptionAround(ByteChannel channel, Cryptor cryptor) {
+ return new DecryptingReadableByteChannel(channel, cryptor, true);
+ }
+
+ EncryptingWritableByteChannel wrapEncryptionAround(ByteChannel channel, Cryptor cryptor) {
return new EncryptingWritableByteChannel(channel, cryptor);
}
}
diff --git a/src/main/java/org/cryptomator/cryptofs/common/Constants.java b/src/main/java/org/cryptomator/cryptofs/common/Constants.java
index 2df843265..b944fac66 100644
--- a/src/main/java/org/cryptomator/cryptofs/common/Constants.java
+++ b/src/main/java/org/cryptomator/cryptofs/common/Constants.java
@@ -25,10 +25,10 @@ private Constants() {
public static final String SYMLINK_FILE_NAME = "symlink.c9r";
public static final String CONTENTS_FILE_NAME = "contents.c9r";
public static final String INFLATED_FILE_NAME = "name.c9s";
- public static final String DIR_BACKUP_FILE_NAME = "dirid.c9r";
+ public static final String DIR_ID_BACKUP_FILE_NAME = "dirid.c9r";
public static final int MAX_SYMLINK_LENGTH = 32767; // max path length on NTFS and FAT32: 32k-1
- public static final int MAX_DIR_FILE_LENGTH = 36; // UUIDv4: hex-encoded 16 byte int + 4 hyphens = 36 ASCII chars
+ public static final int MAX_DIR_ID_LENGTH = 36; // UUIDv4: hex-encoded 16 byte int + 4 hyphens = 36 ASCII chars
public static final int MIN_CIPHER_NAME_LENGTH = 26; //rounded up base64url encoded (16 bytes IV + 0 bytes empty string) + file suffix = 26 ASCII chars
public static final String SEPARATOR = "/";
diff --git a/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java b/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java
index 957ad5552..4a7db9464 100644
--- a/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java
+++ b/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java
@@ -21,7 +21,7 @@
import java.util.stream.Stream;
import static org.cryptomator.cryptofs.common.Constants.DIR_FILE_NAME;
-import static org.cryptomator.cryptofs.common.Constants.MAX_DIR_FILE_LENGTH;
+import static org.cryptomator.cryptofs.common.Constants.MAX_DIR_ID_LENGTH;
import static org.cryptomator.cryptofs.common.Constants.MAX_SYMLINK_LENGTH;
import static org.cryptomator.cryptofs.common.Constants.SYMLINK_FILE_NAME;
@@ -127,7 +127,7 @@ private boolean resolveConflictTrivially(Path canonicalPath, Path conflictingPat
if (!Files.exists(canonicalPath)) {
Files.move(conflictingPath, canonicalPath); // boom. conflict solved.
return true;
- } else if (hasSameFileContent(conflictingPath.resolve(DIR_FILE_NAME), canonicalPath.resolve(DIR_FILE_NAME), MAX_DIR_FILE_LENGTH)) {
+ } else if (hasSameFileContent(conflictingPath.resolve(DIR_FILE_NAME), canonicalPath.resolve(DIR_FILE_NAME), MAX_DIR_ID_LENGTH)) {
LOG.info("Removing conflicting directory {} (identical to {})", conflictingPath, canonicalPath);
MoreFiles.deleteRecursively(conflictingPath, RecursiveDeleteOption.ALLOW_INSECURE);
return true;
diff --git a/src/main/java/org/cryptomator/cryptofs/dir/DirectoryStreamFilters.java b/src/main/java/org/cryptomator/cryptofs/dir/DirectoryStreamFilters.java
index 13f87c112..413601d0f 100644
--- a/src/main/java/org/cryptomator/cryptofs/dir/DirectoryStreamFilters.java
+++ b/src/main/java/org/cryptomator/cryptofs/dir/DirectoryStreamFilters.java
@@ -7,6 +7,6 @@
public interface DirectoryStreamFilters {
- static DirectoryStream.Filter EXCLUDE_DIR_ID_BACKUP = p -> !p.equals(p.resolveSibling(Constants.DIR_BACKUP_FILE_NAME));
+ static DirectoryStream.Filter EXCLUDE_DIR_ID_BACKUP = p -> !p.equals(p.resolveSibling(Constants.DIR_ID_BACKUP_FILE_NAME));
}
diff --git a/src/main/java/org/cryptomator/cryptofs/health/dirid/DirIdCheck.java b/src/main/java/org/cryptomator/cryptofs/health/dirid/DirIdCheck.java
index f283d3121..f553a9f58 100644
--- a/src/main/java/org/cryptomator/cryptofs/health/dirid/DirIdCheck.java
+++ b/src/main/java/org/cryptomator/cryptofs/health/dirid/DirIdCheck.java
@@ -62,7 +62,7 @@ public void check(Path pathToVault, VaultConfig config, Masterkey masterkey, Cry
if (foundDir) {
iter.remove();
var expectedDirVaultRel = Path.of(Constants.DATA_DIR_NAME).resolve(expectedDir);
- if (Files.exists(pathToVault.resolve(expectedDirVaultRel).resolve(Constants.DIR_BACKUP_FILE_NAME))) {
+ if (Files.exists(pathToVault.resolve(expectedDirVaultRel).resolve(Constants.DIR_ID_BACKUP_FILE_NAME))) {
resultCollector.accept(new HealthyDir(dirId, dirFile, expectedDirVaultRel));
} else {
resultCollector.accept(new MissingDirIdBackup(dirId, expectedDirVaultRel));
@@ -116,7 +116,7 @@ private FileVisitResult visitDirFile(Path dirFile, BasicFileAttributes attrs) th
return FileVisitResult.CONTINUE;
}
- if (attrs.size() > Constants.MAX_DIR_FILE_LENGTH) {
+ if (attrs.size() > Constants.MAX_DIR_ID_LENGTH) {
LOG.warn("Encountered dir.c9r file of size {}", attrs.size());
resultCollector.accept(new ObeseDirFile(dirFile, attrs.size()));
} else if (attrs.size() == 0) {
diff --git a/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingContentDir.java b/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingContentDir.java
index 28cd36144..0f9df9681 100644
--- a/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingContentDir.java
+++ b/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingContentDir.java
@@ -51,7 +51,7 @@ void fix(Path pathToVault, Cryptor cryptor) throws IOException {
var dirIdHash = cryptor.fileNameCryptor().hashDirectoryId(dirId);
Path dirPath = pathToVault.resolve(Constants.DATA_DIR_NAME).resolve(dirIdHash.substring(0, 2)).resolve(dirIdHash.substring(2, 32));
Files.createDirectories(dirPath);
- DirectoryIdBackup.backupManually(cryptor, new CiphertextDirectory(dirId, dirPath));
+ DirectoryIdBackup.write(cryptor, new CiphertextDirectory(dirId, dirPath));
}
@Override
diff --git a/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackup.java b/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackup.java
index 9002ba94b..9512d5e69 100644
--- a/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackup.java
+++ b/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackup.java
@@ -12,7 +12,7 @@
import java.util.Optional;
/**
- * The dir id backup file {@value org.cryptomator.cryptofs.common.Constants#DIR_BACKUP_FILE_NAME} is missing.
+ * The dir id backup file {@value org.cryptomator.cryptofs.common.Constants#DIR_ID_BACKUP_FILE_NAME} is missing.
*/
public record MissingDirIdBackup(String dirId, Path contentDir) implements DiagnosticResult {
@@ -29,7 +29,7 @@ public String toString() {
//visible for testing
void fix(Path pathToVault, Cryptor cryptor) throws IOException {
Path absCipherDir = pathToVault.resolve(contentDir);
- DirectoryIdBackup.backupManually(cryptor, new CiphertextDirectory(dirId, absCipherDir));
+ DirectoryIdBackup.write(cryptor, new CiphertextDirectory(dirId, absCipherDir));
}
@Override
diff --git a/src/main/java/org/cryptomator/cryptofs/health/dirid/ObeseDirFile.java b/src/main/java/org/cryptomator/cryptofs/health/dirid/ObeseDirFile.java
index c6b019b06..de6edbaa2 100644
--- a/src/main/java/org/cryptomator/cryptofs/health/dirid/ObeseDirFile.java
+++ b/src/main/java/org/cryptomator/cryptofs/health/dirid/ObeseDirFile.java
@@ -28,7 +28,7 @@ public Severity getSeverity() {
@Override
public String toString() {
- return String.format("Unexpected file size of %s: %d should be ≤ %d", dirFile, size, Constants.MAX_DIR_FILE_LENGTH);
+ return String.format("Unexpected file size of %s: %d should be ≤ %d", dirFile, size, Constants.MAX_DIR_ID_LENGTH);
}
@Override
diff --git a/src/main/java/org/cryptomator/cryptofs/health/dirid/OrphanContentDir.java b/src/main/java/org/cryptomator/cryptofs/health/dirid/OrphanContentDir.java
index 350616a96..d8907406f 100644
--- a/src/main/java/org/cryptomator/cryptofs/health/dirid/OrphanContentDir.java
+++ b/src/main/java/org/cryptomator/cryptofs/health/dirid/OrphanContentDir.java
@@ -8,17 +8,15 @@
import org.cryptomator.cryptofs.common.Constants;
import org.cryptomator.cryptofs.health.api.DiagnosticResult;
import org.cryptomator.cryptolib.api.AuthenticationFailedException;
+import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.cryptolib.api.FileNameCryptor;
import org.cryptomator.cryptolib.api.Masterkey;
-import org.cryptomator.cryptolib.common.ByteBuffers;
-import org.cryptomator.cryptolib.common.DecryptingReadableByteChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.ByteBuffer;
-import java.nio.channels.ByteChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
@@ -89,7 +87,7 @@ private void fix(Path pathToVault, VaultConfig config, Cryptor cryptor) throws I
AtomicInteger dirCounter = new AtomicInteger(1);
AtomicInteger symlinkCounter = new AtomicInteger(1);
String longNameSuffix = createClearnameToBeShortened(config.getShorteningThreshold());
- Optional dirId = retrieveDirId(orphanedDir, cryptor);
+ Optional dirId = retrieveDirId(orphanedDir, cryptor);
try (var orphanedContentStream = Files.newDirectoryStream(orphanedDir, this::matchesEncryptedContentPattern)) {
for (Path orphanedResource : orphanedContentStream) {
@@ -112,7 +110,7 @@ private void fix(Path pathToVault, VaultConfig config, Cryptor cryptor) throws I
adoptOrphanedResource(orphanedResource, newClearName, isShortened, stepParentDir, cryptor.fileNameCryptor(), sha1);
}
}
- Files.deleteIfExists(orphanedDir.resolve(Constants.DIR_BACKUP_FILE_NAME));
+ Files.deleteIfExists(orphanedDir.resolve(Constants.DIR_ID_BACKUP_FILE_NAME));
try (var nonCryptomatorFiles = Files.newDirectoryStream(orphanedDir)) {
for (Path p : nonCryptomatorFiles) {
Files.move(p, stepParentDir.path().resolve(p.getFileName()), LinkOption.NOFOLLOW_LINKS);
@@ -172,7 +170,7 @@ CiphertextDirectory prepareStepParent(Path dataDir, Path cipherRecoveryDir, Cryp
var stepParentCipherDir = new CiphertextDirectory(stepParentUUID, stepParentDir);
//only if it does not exist
try {
- DirectoryIdBackup.backupManually(cryptor, stepParentCipherDir);
+ DirectoryIdBackup.write(cryptor, stepParentCipherDir);
} catch (FileAlreadyExistsException e) {
// already exists due to a previous recovery attempt
}
@@ -180,29 +178,18 @@ CiphertextDirectory prepareStepParent(Path dataDir, Path cipherRecoveryDir, Cryp
}
//visible for testing
- Optional retrieveDirId(Path orphanedDir, Cryptor cryptor) {
- var dirIdFile = orphanedDir.resolve(Constants.DIR_BACKUP_FILE_NAME);
- var dirIdBuffer = ByteBuffer.allocate(36); //a dir id contains at most 36 ascii chars
-
- try (var channel = Files.newByteChannel(dirIdFile, StandardOpenOption.READ); //
- var decryptingChannel = createDecryptingReadableByteChannel(channel, cryptor)) {
- ByteBuffers.fill(decryptingChannel, dirIdBuffer);
- dirIdBuffer.flip();
- } catch (IOException e) {
- LOG.info("Unable to read {}.", dirIdFile, e);
+ Optional retrieveDirId(Path orphanedDir, Cryptor cryptor) {
+ try {
+ byte[] dirId = DirectoryIdBackup.read(cryptor, orphanedDir);
+ return Optional.of(dirId);
+ } catch (IOException | CryptoException | IllegalStateException e) {
+ LOG.info("Unable to retrieve directory id for directory {}", orphanedDir, e);
return Optional.empty();
}
-
- return Optional.of(StandardCharsets.US_ASCII.decode(dirIdBuffer).toString());
- }
-
- //exists and visible for testability
- DecryptingReadableByteChannel createDecryptingReadableByteChannel(ByteChannel channel, Cryptor cryptor) {
- return new DecryptingReadableByteChannel(channel, cryptor, true);
}
//visible for testing
- String decryptFileName(Path orphanedResource, boolean isShortened, String dirId, FileNameCryptor cryptor) throws IOException, AuthenticationFailedException {
+ String decryptFileName(Path orphanedResource, boolean isShortened, byte [] dirId, FileNameCryptor cryptor) throws IOException, AuthenticationFailedException {
final String filenameWithExtension;
if (isShortened) {
filenameWithExtension = Files.readString(orphanedResource.resolve(Constants.INFLATED_FILE_NAME));
@@ -211,7 +198,7 @@ String decryptFileName(Path orphanedResource, boolean isShortened, String dirId,
}
final String filename = filenameWithExtension.substring(0, filenameWithExtension.length() - Constants.CRYPTOMATOR_FILE_SUFFIX.length());
- return cryptor.decryptFilename(BaseEncoding.base64Url(), filename, dirId.getBytes(StandardCharsets.UTF_8));
+ return cryptor.decryptFilename(BaseEncoding.base64Url(), filename, dirId);
}
// visible for testing
diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java
index 3b7d84621..43e9e2cef 100644
--- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java
@@ -1277,7 +1277,7 @@ public void createDirectoryBackupsDirIdInCiphertextDirPath() throws IOException
inTest.createDirectory(path);
verify(readonlyFlag).assertWritable();
- verify(dirIdBackup, Mockito.times(1)).execute(ciphertextDirectoryObject);
+ verify(dirIdBackup, Mockito.times(1)).write(ciphertextDirectoryObject);
}
diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java
index a1dc036da..ab8f8beaf 100644
--- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java
@@ -198,7 +198,7 @@ public void testInitialize() throws IOException, MasterkeyLoadingFailedException
Optional dirIdBackup = Files.list(rootDir.get()).findFirst();
Assertions.assertTrue(dirIdBackup.isPresent());
Assertions.assertTrue(Files.isRegularFile(dirIdBackup.get()));
- Assertions.assertEquals(Constants.DIR_BACKUP_FILE_NAME, dirIdBackup.get().getFileName().toString());
+ Assertions.assertEquals(Constants.DIR_ID_BACKUP_FILE_NAME, dirIdBackup.get().getFileName().toString());
}
@Test
diff --git a/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java
index 12e090a24..713dc7f99 100644
--- a/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java
@@ -169,7 +169,7 @@ private Path firstEmptyCiphertextDirectory() throws IOException {
}
private boolean isEmptyCryptoFsDirectory(Path path) {
- Predicate isIgnoredFile = p -> Constants.DIR_BACKUP_FILE_NAME.equals(p.getFileName().toString());
+ Predicate isIgnoredFile = p -> Constants.DIR_ID_BACKUP_FILE_NAME.equals(p.getFileName().toString());
try (Stream files = Files.list(path)) {
return files.noneMatch(isIgnoredFile.negate());
} catch (IOException e) {
@@ -181,7 +181,7 @@ private boolean isEmptyCryptoFsDirectory(Path path) {
@DisplayName("Tests internal cryptofs directory emptiness definition")
public void testCryptoFsDirEmptiness() throws IOException {
var emptiness = pathToVault.getParent().resolve("emptiness");
- var ignoredFile = emptiness.resolve(Constants.DIR_BACKUP_FILE_NAME);
+ var ignoredFile = emptiness.resolve(Constants.DIR_ID_BACKUP_FILE_NAME);
Files.createDirectory(emptiness);
Files.createFile(ignoredFile);
diff --git a/src/test/java/org/cryptomator/cryptofs/DirectoryIdBackupTest.java b/src/test/java/org/cryptomator/cryptofs/DirectoryIdBackupTest.java
index 58739e909..cd0d16794 100644
--- a/src/test/java/org/cryptomator/cryptofs/DirectoryIdBackupTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/DirectoryIdBackupTest.java
@@ -1,13 +1,16 @@
package org.cryptomator.cryptofs;
import org.cryptomator.cryptofs.common.Constants;
+import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.Cryptor;
+import org.cryptomator.cryptolib.common.DecryptingReadableByteChannel;
import org.cryptomator.cryptolib.common.EncryptingWritableByteChannel;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
-import org.mockito.MockedStatic;
import org.mockito.Mockito;
import java.io.IOException;
@@ -15,6 +18,10 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
public class DirectoryIdBackupTest {
@@ -22,8 +29,6 @@ public class DirectoryIdBackupTest {
Path contentPath;
private String dirId = "12345678";
- private CiphertextDirectory ciphertextDirectoryObject;
- private EncryptingWritableByteChannel encChannel;
private Cryptor cryptor;
private DirectoryIdBackup dirIdBackup;
@@ -31,37 +36,112 @@ public class DirectoryIdBackupTest {
@BeforeEach
public void init() {
- ciphertextDirectoryObject = new CiphertextDirectory(dirId, contentPath);
cryptor = Mockito.mock(Cryptor.class);
- encChannel = Mockito.mock(EncryptingWritableByteChannel.class);
-
dirIdBackup = new DirectoryIdBackup(cryptor);
}
- @Test
- public void testIdFileCreated() throws IOException {
- try (MockedStatic backupMock = Mockito.mockStatic(DirectoryIdBackup.class)) {
- backupMock.when(() -> DirectoryIdBackup.wrapEncryptionAround(Mockito.any(), Mockito.eq(cryptor))).thenReturn(encChannel);
+ @Nested
+ public class Write {
+
+ private CiphertextDirectory ciphertextDirectoryObject;
+ private EncryptingWritableByteChannel encChannel;
+
+ @BeforeEach
+ public void beforeEachWriteTest() {
+ ciphertextDirectoryObject = new CiphertextDirectory(dirId, contentPath);
+ encChannel = Mockito.mock(EncryptingWritableByteChannel.class);
+ }
+
+ @Test
+ public void testIdFileCreated() throws IOException {
+ var dirIdBackupSpy = spy(dirIdBackup);
+ Mockito.doReturn(encChannel).when(dirIdBackupSpy).wrapEncryptionAround(Mockito.any(), Mockito.eq(cryptor));
Mockito.when(encChannel.write(Mockito.any())).thenReturn(0);
- dirIdBackup.execute(ciphertextDirectoryObject);
+ dirIdBackupSpy.write(ciphertextDirectoryObject);
- Assertions.assertTrue(Files.exists(contentPath.resolve(Constants.DIR_BACKUP_FILE_NAME)));
+ Assertions.assertTrue(Files.exists(contentPath.resolve(Constants.DIR_ID_BACKUP_FILE_NAME)));
}
+
+ @Test
+ public void testContentIsWritten() throws IOException {
+ var dirIdBackupSpy = spy(dirIdBackup);
+ Mockito.doReturn(encChannel).when(dirIdBackupSpy).wrapEncryptionAround(Mockito.any(), Mockito.eq(cryptor));
+ Mockito.when(encChannel.write(Mockito.any())).thenReturn(0);
+ var expectedWrittenContent = ByteBuffer.wrap(dirId.getBytes(StandardCharsets.US_ASCII));
+
+ dirIdBackupSpy.write(ciphertextDirectoryObject);
+
+ Mockito.verify(encChannel, Mockito.times(1)).write(Mockito.argThat(b -> b.equals(expectedWrittenContent)));
+ }
+ //TODO: test, what happens if file already exists?
}
- @Test
- public void testContentIsWritten() throws IOException {
- Mockito.when(encChannel.write(Mockito.any())).thenReturn(0);
- var expectedWrittenContent = ByteBuffer.wrap(dirId.getBytes(StandardCharsets.US_ASCII));
+ @Nested
+ public class Read {
- try (MockedStatic backupMock = Mockito.mockStatic(DirectoryIdBackup.class)) {
- backupMock.when(() -> DirectoryIdBackup.wrapEncryptionAround(Mockito.any(), Mockito.eq(cryptor))).thenReturn(encChannel);
+ private DecryptingReadableByteChannel decChannel;
- dirIdBackup.execute(ciphertextDirectoryObject);
+ @BeforeEach
+ public void beforeEachRead() throws IOException {
+ var backupFile = contentPath.resolve(Constants.DIR_ID_BACKUP_FILE_NAME);
+ Files.writeString(backupFile, dirId, StandardCharsets.US_ASCII, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
- Mockito.verify(encChannel, Mockito.times(1)).write(Mockito.argThat(b -> b.equals(expectedWrittenContent)));
+ decChannel = mock(DecryptingReadableByteChannel.class);
+ }
+
+ @Test
+ @DisplayName("If the directory id is longer than 36 characters, throw IllegalStateException")
+ public void contentLongerThan36Chars() throws IOException {
+ var dirIdBackupSpy = spy(dirIdBackup);
+ Mockito.when(dirIdBackupSpy.wrapDecryptionAround(Mockito.any(), Mockito.eq(cryptor))).thenReturn(decChannel);
+ Mockito.when(decChannel.read(Mockito.any())).thenReturn(Constants.MAX_DIR_ID_LENGTH + 1);
+ Assertions.assertThrows(IllegalStateException.class, () -> dirIdBackupSpy.read(contentPath));
+ }
+
+ @Test
+ @DisplayName("If the backup file cannot be decrypted, a CryptoException is thrown")
+ public void invalidEncryptionThrowsCryptoException() throws IOException {
+ var dirIdBackupSpy = spy(dirIdBackup);
+ var expectedException = new MyCryptoException();
+ Mockito.when(dirIdBackupSpy.wrapDecryptionAround(Mockito.any(), Mockito.eq(cryptor))).thenReturn(decChannel);
+ Mockito.when(decChannel.read(Mockito.any())).thenThrow(expectedException);
+ var actual = Assertions.assertThrows(CryptoException.class, () -> dirIdBackupSpy.read(contentPath));
+ Assertions.assertEquals(expectedException, actual);
+ }
+
+ static class MyCryptoException extends CryptoException {
+
+ }
+
+ @Test
+ @DisplayName("IOException accessing the file is rethrown")
+ public void ioException() throws IOException {
+ var dirIdBackupSpy = spy(dirIdBackup);
+ var expectedException = new IOException("my oh my");
+ Mockito.when(dirIdBackupSpy.wrapDecryptionAround(Mockito.any(), Mockito.eq(cryptor))).thenReturn(decChannel);
+ Mockito.when(decChannel.read(Mockito.any())).thenThrow(expectedException);
+ var actual = Assertions.assertThrows(IOException.class, () -> dirIdBackupSpy.read(contentPath));
+ Assertions.assertEquals(expectedException, actual);
+ }
+
+ @Test
+ @DisplayName("Valid dir id is read from the backup file")
+ public void success() throws IOException {
+ var dirIdBackupSpy = spy(dirIdBackup);
+ var expectedArray = dirId.getBytes(StandardCharsets.US_ASCII);
+
+ Mockito.when(dirIdBackupSpy.wrapDecryptionAround(Mockito.any(), Mockito.eq(cryptor))).thenReturn(decChannel);
+ Mockito.doAnswer(invocationOnMock -> {
+ var buf = (ByteBuffer) invocationOnMock.getArgument(0);
+ buf.put(expectedArray);
+ return expectedArray.length;
+ }).when(decChannel).read(Mockito.any());
+
+ var readDirId = dirIdBackupSpy.read(contentPath);
+ Assertions.assertArrayEquals(expectedArray, readDirId);
}
}
+
}
diff --git a/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingContentDirTest.java b/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingContentDirTest.java
index 10e9ba35f..240fe666d 100644
--- a/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingContentDirTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingContentDirTest.java
@@ -55,13 +55,13 @@ public void testFix() throws IOException {
var dirIdHash = "ridiculous-32-char-pseudo-hashhh";
Mockito.doReturn(dirIdHash).when(fileNameCryptor).hashDirectoryId(dirId);
try (var dirIdBackupMock = Mockito.mockStatic(DirectoryIdBackup.class)) {
- dirIdBackupMock.when(() -> DirectoryIdBackup.backupManually(Mockito.any(), Mockito.any())).thenAnswer(Answers.RETURNS_SMART_NULLS);
+ dirIdBackupMock.when(() -> DirectoryIdBackup.write(Mockito.any(), Mockito.any())).thenAnswer(Answers.RETURNS_SMART_NULLS);
result.fix(pathToVault, cryptor);
var expectedPath = pathToVault.resolve("d/ri/diculous-32-char-pseudo-hashhh");
ArgumentMatcher cipherDirMatcher = obj -> obj.dirId().equals(dirId) && obj.path().endsWith(expectedPath);
- dirIdBackupMock.verify(() -> DirectoryIdBackup.backupManually(Mockito.eq(cryptor), Mockito.argThat(cipherDirMatcher)), Mockito.times(1));
+ dirIdBackupMock.verify(() -> DirectoryIdBackup.write(Mockito.eq(cryptor), Mockito.argThat(cipherDirMatcher)), Mockito.times(1));
var attr = Assertions.assertDoesNotThrow(() -> Files.readAttributes(expectedPath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS));
Assertions.assertTrue(attr.isDirectory());
}
@@ -73,7 +73,7 @@ public void testFixFailsOnFailingDirIdFile() throws IOException {
var dirIdHash = "ridiculous-32-char-pseudo-hashhh";
try (var dirIdBackupMock = Mockito.mockStatic(DirectoryIdBackup.class)) {
Mockito.doReturn(dirIdHash).when(fileNameCryptor).hashDirectoryId(dirId);
- dirIdBackupMock.when(() -> DirectoryIdBackup.backupManually(Mockito.any(), Mockito.any())).thenThrow(new IOException("Access denied"));
+ dirIdBackupMock.when(() -> DirectoryIdBackup.write(Mockito.any(), Mockito.any())).thenThrow(new IOException("Access denied"));
Assertions.assertThrows(IOException.class, () -> result.fix(pathToVault, cryptor));
}
diff --git a/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackupTest.java b/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackupTest.java
index cb97d0b72..6d173b498 100644
--- a/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackupTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackupTest.java
@@ -36,7 +36,7 @@ public void testFix() throws IOException {
Path cipherDir = Path.of("d/ri/diculous-30-char-pseudo-hash");
String dirId = "1234-456789-1234";
try (var dirIdBackupMock = Mockito.mockStatic(DirectoryIdBackup.class)) {
- dirIdBackupMock.when(() -> DirectoryIdBackup.backupManually(Mockito.any(), Mockito.any())).thenAnswer(Answers.RETURNS_SMART_NULLS);
+ dirIdBackupMock.when(() -> DirectoryIdBackup.write(Mockito.any(), Mockito.any())).thenAnswer(Answers.RETURNS_SMART_NULLS);
Cryptor cryptor = Mockito.mock(Cryptor.class);
result = new MissingDirIdBackup(dirId, cipherDir);
@@ -44,7 +44,7 @@ public void testFix() throws IOException {
var expectedPath = pathToVault.resolve(cipherDir);
ArgumentMatcher cipherDirMatcher = obj -> obj.dirId().equals(dirId) && obj.path().isAbsolute() && obj.path().equals(expectedPath);
- dirIdBackupMock.verify(() -> DirectoryIdBackup.backupManually(Mockito.eq(cryptor), Mockito.argThat(cipherDirMatcher)), Mockito.times(1));
+ dirIdBackupMock.verify(() -> DirectoryIdBackup.write(Mockito.eq(cryptor), Mockito.argThat(cipherDirMatcher)), Mockito.times(1));
}
}
}
diff --git a/src/test/java/org/cryptomator/cryptofs/health/dirid/OrphanDirTest.java b/src/test/java/org/cryptomator/cryptofs/health/dirid/OrphanContentDirTest.java
similarity index 88%
rename from src/test/java/org/cryptomator/cryptofs/health/dirid/OrphanDirTest.java
rename to src/test/java/org/cryptomator/cryptofs/health/dirid/OrphanContentDirTest.java
index 9f9c93b27..48a4954b0 100644
--- a/src/test/java/org/cryptomator/cryptofs/health/dirid/OrphanDirTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/health/dirid/OrphanContentDirTest.java
@@ -6,33 +6,33 @@
import org.cryptomator.cryptofs.VaultConfig;
import org.cryptomator.cryptofs.common.Constants;
import org.cryptomator.cryptolib.api.AuthenticationFailedException;
+import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.cryptolib.api.FileNameCryptor;
import org.cryptomator.cryptolib.api.Masterkey;
-import org.cryptomator.cryptolib.common.DecryptingReadableByteChannel;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.FieldSource;
import org.mockito.Mockito;
import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.SeekableByteChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.security.MessageDigest;
+import java.util.List;
import java.util.Optional;
import java.util.UUID;
-import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
-public class OrphanDirTest {
+public class OrphanContentDirTest {
@TempDir
public Path pathToVault;
@@ -162,11 +162,11 @@ public void testPrepareStepParent() throws IOException {
UUID uuid = Mockito.mock(UUID.class);
uuidClass.when(UUID::randomUUID).thenReturn(uuid);
Mockito.doReturn("aaaaaa").when(uuid).toString();
- dirIdBackupClass.when(() -> DirectoryIdBackup.backupManually(Mockito.eq(cryptor), Mockito.any())).thenAnswer(invocation -> null);
+ dirIdBackupClass.when(() -> DirectoryIdBackup.write(Mockito.eq(cryptor), Mockito.any())).thenAnswer(invocation -> null);
result.prepareStepParent(dataDir, cipherRecovery, cryptor, clearStepParentName);
- dirIdBackupClass.verify(() -> DirectoryIdBackup.backupManually(Mockito.eq(cryptor), Mockito.any()), Mockito.times(1));
+ dirIdBackupClass.verify(() -> DirectoryIdBackup.write(Mockito.eq(cryptor), Mockito.any()), Mockito.times(1));
}
Assertions.assertEquals("aaaaaa", Files.readString(cipherRecovery.resolve("2.c9r/dir.c9r"), StandardCharsets.UTF_8));
Assertions.assertTrue(Files.isDirectory(pathToVault.resolve("d/22/2222")));
@@ -186,11 +186,11 @@ public void testPrepareStepParentExistingStepParentDir() throws IOException {
UUID uuid = Mockito.mock(UUID.class);
uuidClass.when(UUID::randomUUID).thenReturn(uuid);
Mockito.doReturn("aaaaaa").when(uuid).toString();
- dirIdBackupClass.when(() -> DirectoryIdBackup.backupManually(Mockito.eq(cryptor), Mockito.any())).thenThrow(new FileAlreadyExistsException("dirId file exists"));
+ dirIdBackupClass.when(() -> DirectoryIdBackup.write(Mockito.eq(cryptor), Mockito.any())).thenThrow(new FileAlreadyExistsException("dirId file exists"));
result.prepareStepParent(dataDir, cipherRecovery, cryptor, clearStepParentName);
- dirIdBackupClass.verify(() -> DirectoryIdBackup.backupManually(Mockito.eq(cryptor), Mockito.any()), Mockito.times(1));
+ dirIdBackupClass.verify(() -> DirectoryIdBackup.write(Mockito.eq(cryptor), Mockito.any()), Mockito.times(1));
}
Assertions.assertEquals("aaaaaa", Files.readString(cipherRecovery.resolve("2.c9r/dir.c9r"), StandardCharsets.UTF_8));
Assertions.assertTrue(Files.isDirectory(pathToVault.resolve("d/22/2222")));
@@ -210,11 +210,11 @@ public void testPrepareStepParentOrphanedStepParentDir() throws IOException {
UUID uuid = Mockito.mock(UUID.class);
uuidClass.when(UUID::randomUUID).thenReturn(uuid);
Mockito.doReturn("aaaaaa").when(uuid).toString();
- dirIdBackupClass.when(() -> DirectoryIdBackup.backupManually(Mockito.eq(cryptor), Mockito.any())).thenAnswer(invocation -> null);
+ dirIdBackupClass.when(() -> DirectoryIdBackup.write(Mockito.eq(cryptor), Mockito.any())).thenAnswer(invocation -> null);
result.prepareStepParent(dataDir, cipherRecovery, cryptor, clearStepParentName);
- dirIdBackupClass.verify(() -> DirectoryIdBackup.backupManually(Mockito.eq(cryptor), Mockito.any()), Mockito.times(1));
+ dirIdBackupClass.verify(() -> DirectoryIdBackup.write(Mockito.eq(cryptor), Mockito.any()), Mockito.times(1));
}
Assertions.assertEquals("aaaaaa", Files.readString(cipherRecovery.resolve("2.c9r/dir.c9r"), StandardCharsets.UTF_8));
Assertions.assertTrue(Files.isDirectory(pathToVault.resolve("d/22/2222")));
@@ -227,46 +227,38 @@ class RetrieveDirIdTests {
private OrphanContentDir resultSpy;
+ static class MyCryptoException extends CryptoException {
+
+ }
+
+ static List expectedExceptions = List.of(new IOException(), new IllegalStateException(), new MyCryptoException());
+
@BeforeEach
public void init() {
resultSpy = Mockito.spy(result);
}
@Test
- @DisplayName("retrieveDirId extracts directory id of cipher-dir/dirId.c9r")
- public void testRetrieveDirIdSuccess() throws IOException {
- var dirIdFile = cipherOrphan.resolve(Constants.DIR_BACKUP_FILE_NAME);
- var dirId = "random-uuid-with-at-most-36chars";
-
- Files.writeString(dirIdFile, dirId, StandardCharsets.US_ASCII, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE);
- DecryptingReadableByteChannel dirIdReadChannel = Mockito.mock(DecryptingReadableByteChannel.class);
-
- Mockito.doReturn(dirIdReadChannel).when(resultSpy).createDecryptingReadableByteChannel(Mockito.any(), Mockito.eq(cryptor));
- AtomicInteger readBytesInMockedChannel = new AtomicInteger(0);
- //in every invocation the channel position is updated, simulating a stateful channel
- Mockito.doAnswer(invocationOnMock -> {
- ByteBuffer buf = invocationOnMock.getArgument(0);
- try (SeekableByteChannel channel = Files.newByteChannel(dirIdFile, StandardOpenOption.READ)) {
- channel.position(readBytesInMockedChannel.get());
- readBytesInMockedChannel.getAndSet(channel.read(buf));
- return readBytesInMockedChannel.get();
- }
- }).when(dirIdReadChannel).read(Mockito.any());
-
- Mockito.when(fileNameCryptor.hashDirectoryId(dirId)).thenReturn("333333");
-
- var maybeDirId = resultSpy.retrieveDirId(cipherOrphan, cryptor);
-
- Assertions.assertTrue(maybeDirId.isPresent());
- Assertions.assertEquals(dirId, maybeDirId.get());
+ @DisplayName("Successful reading dirId from backup file")
+ public void success() {
+ var dirId = new byte[]{'f', 'o', 'o'};
+ try (var dirIdBackupMock = Mockito.mockStatic(DirectoryIdBackup.class)) {
+ dirIdBackupMock.when(() -> DirectoryIdBackup.read(cryptor, cipherOrphan)).thenReturn(dirId);
+ var result = resultSpy.retrieveDirId(cipherOrphan, cryptor);
+ Assertions.assertTrue(result.isPresent());
+ Assertions.assertArrayEquals(dirId, result.get());
+ }
}
- @Test
- @DisplayName("retrieveDirId returns an empty optional if cipher-dir/dirId.c9r cannot be read")
- public void testRetrieveDirIdIOExceptionReadingFile() throws IOException {
- var notExistingResult = resultSpy.retrieveDirId(cipherOrphan, cryptor);
-
- Assertions.assertTrue(notExistingResult.isEmpty());
+ @ParameterizedTest
+ @DisplayName("retrieveDirId returns an empty optional on any exception")
+ @FieldSource("expectedExceptions")
+ public void testRetrieveDirIdIOExceptionReadingFile(Throwable t) throws IOException {
+ try (var dirIdBackupMock = Mockito.mockStatic(DirectoryIdBackup.class)) {
+ dirIdBackupMock.when(() -> DirectoryIdBackup.read(cryptor, cipherOrphan)).thenThrow(t);
+ var notExistingResult = resultSpy.retrieveDirId(cipherOrphan, cryptor);
+ Assertions.assertTrue(notExistingResult.isEmpty());
+ }
}
}
@@ -283,7 +275,7 @@ void testRestoreFilenameNormalSuccess() throws IOException {
//by using Mockito.eq() in filename parameter Mockito.verfiy() not necessary
Mockito.when(fileNameCryptor.decryptFilename(Mockito.any(), Mockito.eq("orphan"), Mockito.any())).thenReturn("theTrueName.txt");
- String decryptedFile = result.decryptFileName(oldCipherPath, false, "someDirId", fileNameCryptor);
+ String decryptedFile = result.decryptFileName(oldCipherPath, false, new byte[]{}, fileNameCryptor);
Assertions.assertEquals("theTrueName.txt", decryptedFile);
}
@@ -299,7 +291,7 @@ void testRestoreFilenameShortenedSuccess() throws IOException {
//by using Mockito.eq() in filename parameter Mockito.verfiy() not necessary
Mockito.when(fileNameCryptor.decryptFilename(Mockito.any(), Mockito.eq("OrphanWithLongestName"), Mockito.any())).thenReturn("theRealLongName.txt");
- String decryptedFile = result.decryptFileName(oldCipherPath, true, "someDirId", fileNameCryptor);
+ String decryptedFile = result.decryptFileName(oldCipherPath, true, new byte[]{}, fileNameCryptor);
Assertions.assertEquals("theRealLongName.txt", decryptedFile);
}
@@ -310,7 +302,7 @@ void testRestoreFilenameShortenedIOException() throws IOException {
Path oldCipherPath = cipherOrphan.resolve("hashOfOrphanWithLongestName.c9r");
Files.createDirectory(oldCipherPath);
- Assertions.assertThrows(IOException.class, () -> result.decryptFileName(oldCipherPath, true, "someDirId", fileNameCryptor));
+ Assertions.assertThrows(IOException.class, () -> result.decryptFileName(oldCipherPath, true, new byte[]{}, fileNameCryptor));
}
}
@@ -438,9 +430,9 @@ public void testFixContinuesOnNotRecoverableFilename() throws IOException {
Path orphan2 = cipherOrphan.resolve("orphan2_with_at_least_26chars.c9s");
Files.createFile(orphan1);
Files.createDirectories(orphan2);
- Files.createFile(cipherOrphan.resolve(Constants.DIR_BACKUP_FILE_NAME));
+ Files.createFile(cipherOrphan.resolve(Constants.DIR_ID_BACKUP_FILE_NAME));
- var dirId = Optional.of("trololo-id");
+ var dirId = Optional.of(new byte[]{'t', 'r', 'o', 'l', 'o', 'l', 'o'});
CiphertextDirectory stepParentDir = new CiphertextDirectory("aaaaaa", dataDir.resolve("22/2222"));
@@ -477,9 +469,9 @@ public void testFixWithDirId() throws IOException {
Path orphan2 = cipherOrphan.resolve("orphan2_with_at_least_26chars.c9s");
Files.createFile(orphan1);
Files.createDirectories(orphan2);
- Files.createFile(cipherOrphan.resolve(Constants.DIR_BACKUP_FILE_NAME));
+ Files.createFile(cipherOrphan.resolve(Constants.DIR_ID_BACKUP_FILE_NAME));
- var dirId = Optional.of("trololo-id");
+ var dirId = Optional.of(new byte[]{'t', 'r', 'o', 'l', 'o', 'l', 'o'});
CiphertextDirectory stepParentDir = new CiphertextDirectory("aaaaaa", dataDir.resolve("22/2222"));
@@ -500,7 +492,7 @@ public void testFixWithDirId() throws IOException {
resultSpy.fix(pathToVault, config, masterkey, cryptor);
- Mockito.verify(resultSpy, Mockito.never()).adoptOrphanedResource(Mockito.eq(cipherOrphan.resolve(Constants.DIR_BACKUP_FILE_NAME)), Mockito.any(), Mockito.anyBoolean(), Mockito.eq(stepParentDir), Mockito.eq(fileNameCryptor), Mockito.any());
+ Mockito.verify(resultSpy, Mockito.never()).adoptOrphanedResource(Mockito.eq(cipherOrphan.resolve(Constants.DIR_ID_BACKUP_FILE_NAME)), Mockito.any(), Mockito.anyBoolean(), Mockito.eq(stepParentDir), Mockito.eq(fileNameCryptor), Mockito.any());
Mockito.verify(resultSpy, Mockito.times(1)).adoptOrphanedResource(Mockito.eq(orphan1), Mockito.eq(lostName1), Mockito.anyBoolean(), Mockito.eq(stepParentDir), Mockito.eq(fileNameCryptor), Mockito.any());
Mockito.verify(resultSpy, Mockito.times(1)).adoptOrphanedResource(Mockito.eq(orphan2), Mockito.eq(lostName2), Mockito.anyBoolean(), Mockito.eq(stepParentDir), Mockito.eq(fileNameCryptor), Mockito.any());
Assertions.assertTrue(Files.notExists(cipherOrphan));
@@ -520,9 +512,9 @@ public void testFixWithNonCryptomatorFiles() throws IOException {
Files.createFile(orphan1);
Files.createDirectories(orphan2);
Files.createFile(unrelated);
- Files.createFile(cipherOrphan.resolve(Constants.DIR_BACKUP_FILE_NAME));
+ Files.createFile(cipherOrphan.resolve(Constants.DIR_ID_BACKUP_FILE_NAME));
- var dirId = Optional.of("trololo-id");
+ var dirId = Optional.of(new byte[]{'t', 'r', 'o', 'l', 'o', 'l', 'o'});
CiphertextDirectory stepParentDir = new CiphertextDirectory("aaaaaa", dataDir.resolve("22/2222"));
Files.createDirectories(stepParentDir.path()); //needs to be created here, otherwise the Files.move(non-crypto-resource, stepparent) will fail
@@ -605,5 +597,4 @@ public void testFixOrphanedRecoveryDir() throws IOException {
Mockito.verify(resultSpy, Mockito.never()).adoptOrphanedResource(Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.any(), Mockito.eq(fileNameCryptor), Mockito.any());
Mockito.verify(resultSpy).prepareRecoveryDir(pathToVault, fileNameCryptor);
}
-
}