flags = EnumSet.copyOf(originalProperties.flags());
- flags.add(CryptoFileSystemProperties.FileSystemFlags.READONLY);
- return CryptoFileSystemProperties.cryptoFileSystemPropertiesFrom(originalProperties).withFlags(flags).build();
- }
- } else {
- return originalProperties;
- }
- }
-
public void remove(CryptoFileSystemImpl cryptoFileSystem) {
fileSystems.values().remove(cryptoFileSystem);
}
diff --git a/src/main/java/org/cryptomator/cryptofs/DirectoryIdBackup.java b/src/main/java/org/cryptomator/cryptofs/DirectoryIdBackup.java
index 6dc2f7fe..5be4f60f 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/FileNameDecryptor.java b/src/main/java/org/cryptomator/cryptofs/FileNameDecryptor.java
new file mode 100644
index 00000000..ff312c8a
--- /dev/null
+++ b/src/main/java/org/cryptomator/cryptofs/FileNameDecryptor.java
@@ -0,0 +1,99 @@
+package org.cryptomator.cryptofs;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.io.BaseEncoding;
+import org.cryptomator.cryptofs.common.Constants;
+import org.cryptomator.cryptofs.common.StringUtils;
+import org.cryptomator.cryptolib.api.CryptoException;
+import org.cryptomator.cryptolib.api.Cryptor;
+import org.cryptomator.cryptolib.api.FileNameCryptor;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.nio.file.FileSystemException;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.util.stream.Stream;
+
+/**
+ * @see CryptoFileSystem#getCleartextName(Path)
+ */
+@CryptoFileSystemScoped
+class FileNameDecryptor {
+
+ private final DirectoryIdBackup dirIdBackup;
+ private final LongFileNameProvider longFileNameProvider;
+ private final Path vaultPath;
+ private final FileNameCryptor fileNameCryptor;
+
+ @Inject
+ public FileNameDecryptor(@PathToVault Path vaultPath, Cryptor cryptor, DirectoryIdBackup dirIdBackup, LongFileNameProvider longFileNameProvider) {
+ this.vaultPath = vaultPath;
+ this.fileNameCryptor = cryptor.fileNameCryptor();
+ this.dirIdBackup = dirIdBackup;
+ this.longFileNameProvider = longFileNameProvider;
+ }
+
+ public String decryptFilename(Path ciphertextNode) throws IOException, UnsupportedOperationException {
+ validatePath(ciphertextNode.toAbsolutePath());
+ return decryptFilenameInternal(ciphertextNode);
+ }
+
+ @VisibleForTesting
+ String decryptFilenameInternal(Path ciphertextNode) throws IOException, UnsupportedOperationException {
+ byte[] dirId = null;
+ try {
+ dirId = dirIdBackup.read(ciphertextNode);
+ } catch (NoSuchFileException e) {
+ throw new UnsupportedOperationException("Directory does not have a " + Constants.DIR_ID_BACKUP_FILE_NAME + " file.");
+ } catch (CryptoException | IllegalStateException e) {
+ throw new FileSystemException(ciphertextNode.toString(), null, "Decryption of dirId backup file failed:" + e);
+ }
+ var fullCipherNodeName = ciphertextNode.getFileName().toString();
+ var cipherNodeExtension = fullCipherNodeName.substring(fullCipherNodeName.length() - 4);
+
+ String actualEncryptedName = switch (cipherNodeExtension) {
+ case Constants.CRYPTOMATOR_FILE_SUFFIX -> StringUtils.removeEnd(fullCipherNodeName, Constants.CRYPTOMATOR_FILE_SUFFIX);
+ case Constants.DEFLATED_FILE_SUFFIX -> longFileNameProvider.inflate(ciphertextNode);
+ default -> throw new IllegalStateException("SHOULD NOT REACH HERE");
+ };
+ try {
+ return fileNameCryptor.decryptFilename(BaseEncoding.base64Url(), actualEncryptedName, dirId);
+ } catch (CryptoException e) {
+ throw new FileSystemException(ciphertextNode.toString(), null, "Filname decryption failed:" + e);
+ }
+ }
+
+ @VisibleForTesting
+ void validatePath(Path absolutePath) {
+ if (!belongsToVault(absolutePath)) {
+ throw new IllegalArgumentException("Node %s is not a part of vault %s".formatted(absolutePath, vaultPath));
+ }
+ if (!isAtCipherNodeLevel(absolutePath)) {
+ throw new IllegalArgumentException("Node %s is not located at depth 4 from vault storage root".formatted(absolutePath));
+ }
+ if (!(hasCipherNodeExtension(absolutePath) && hasMinimumFileNameLength(absolutePath))) {
+ throw new IllegalArgumentException("Node %s does not end with %s or %s or filename is shorter than %d characters.".formatted(absolutePath, Constants.CRYPTOMATOR_FILE_SUFFIX, Constants.DEFLATED_FILE_SUFFIX, Constants.MIN_CIPHER_NAME_LENGTH));
+ }
+ }
+
+ boolean hasCipherNodeExtension(Path p) {
+ var name = p.getFileName();
+ return name != null && Stream.of(Constants.CRYPTOMATOR_FILE_SUFFIX, Constants.DEFLATED_FILE_SUFFIX).anyMatch(name.toString()::endsWith);
+ }
+
+ boolean isAtCipherNodeLevel(Path absolutPah) {
+ if (!absolutPah.isAbsolute()) {
+ throw new IllegalArgumentException("Path " + absolutPah + "must be absolute");
+ }
+ return absolutPah.subpath(vaultPath.getNameCount(), absolutPah.getNameCount()).getNameCount() == 4;
+ }
+
+ boolean hasMinimumFileNameLength(Path p) {
+ return p.getFileName().toString().length() >= Constants.MIN_CIPHER_NAME_LENGTH;
+ }
+
+ boolean belongsToVault(Path p) {
+ return p.startsWith(vaultPath);
+ }
+}
diff --git a/src/main/java/org/cryptomator/cryptofs/ch/ChannelCloseListener.java b/src/main/java/org/cryptomator/cryptofs/ch/ChannelCloseListener.java
deleted file mode 100644
index 3ee1c008..00000000
--- a/src/main/java/org/cryptomator/cryptofs/ch/ChannelCloseListener.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.cryptomator.cryptofs.ch;
-
-
-@FunctionalInterface
-public interface ChannelCloseListener {
-
- void closed(CleartextFileChannel channel);
-
-}
\ No newline at end of file
diff --git a/src/main/java/org/cryptomator/cryptofs/ch/ChannelComponent.java b/src/main/java/org/cryptomator/cryptofs/ch/ChannelComponent.java
index a22b0fa4..2447c10a 100644
--- a/src/main/java/org/cryptomator/cryptofs/ch/ChannelComponent.java
+++ b/src/main/java/org/cryptomator/cryptofs/ch/ChannelComponent.java
@@ -5,6 +5,7 @@
import org.cryptomator.cryptofs.EffectiveOpenOptions;
import java.nio.channels.FileChannel;
+import java.util.function.Consumer;
@ChannelScoped
@Subcomponent
@@ -17,7 +18,7 @@ interface Factory {
ChannelComponent create(@BindsInstance FileChannel ciphertextChannel, //
@BindsInstance EffectiveOpenOptions options, //
- @BindsInstance ChannelCloseListener listener); //
+ @BindsInstance Consumer closeListener); //
}
}
diff --git a/src/main/java/org/cryptomator/cryptofs/ch/CleartextFileChannel.java b/src/main/java/org/cryptomator/cryptofs/ch/CleartextFileChannel.java
index 44d165a8..f40a34d7 100644
--- a/src/main/java/org/cryptomator/cryptofs/ch/CleartextFileChannel.java
+++ b/src/main/java/org/cryptomator/cryptofs/ch/CleartextFileChannel.java
@@ -34,6 +34,7 @@
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReadWriteLock;
+import java.util.function.Consumer;
import static java.lang.Math.max;
import static java.lang.Math.min;
@@ -53,11 +54,11 @@ public class CleartextFileChannel extends AbstractFileChannel {
private final AtomicLong fileSize;
private final AtomicReference lastModified;
private final ExceptionsDuringWrite exceptionsDuringWrite;
- private final ChannelCloseListener closeListener;
+ private final Consumer closeListener;
private final CryptoFileSystemStats stats;
@Inject
- public CleartextFileChannel(FileChannel ciphertextFileChannel, FileHeaderHolder fileHeaderHolder, ReadWriteLock readWriteLock, Cryptor cryptor, ChunkCache chunkCache, BufferPool bufferPool, EffectiveOpenOptions options, @OpenFileSize AtomicLong fileSize, @OpenFileModifiedDate AtomicReference lastModified, @CurrentOpenFilePath AtomicReference currentPath, ExceptionsDuringWrite exceptionsDuringWrite, ChannelCloseListener closeListener, CryptoFileSystemStats stats) {
+ public CleartextFileChannel(FileChannel ciphertextFileChannel, FileHeaderHolder fileHeaderHolder, ReadWriteLock readWriteLock, Cryptor cryptor, ChunkCache chunkCache, BufferPool bufferPool, EffectiveOpenOptions options, @OpenFileSize AtomicLong fileSize, @OpenFileModifiedDate AtomicReference lastModified, @CurrentOpenFilePath AtomicReference currentPath, ExceptionsDuringWrite exceptionsDuringWrite, Consumer closeListener, CryptoFileSystemStats stats) {
super(readWriteLock);
this.ciphertextFileChannel = ciphertextFileChannel;
this.fileHeaderHolder = fileHeaderHolder;
@@ -327,7 +328,7 @@ long beginOfChunk(long cleartextPos) {
protected void implCloseChannel() throws IOException {
var closeActions = List.of(this::flush, //
super::implCloseChannel, //
- () -> closeListener.closed(this), //
+ () -> closeListener.accept(ciphertextFileChannel),
ciphertextFileChannel::close, //
this::tryPersistLastModified);
tryAll(closeActions.iterator());
diff --git a/src/main/java/org/cryptomator/cryptofs/common/Constants.java b/src/main/java/org/cryptomator/cryptofs/common/Constants.java
index 2df84326..291eaf22 100644
--- a/src/main/java/org/cryptomator/cryptofs/common/Constants.java
+++ b/src/main/java/org/cryptomator/cryptofs/common/Constants.java
@@ -25,11 +25,13 @@ 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 MIN_CIPHER_NAME_LENGTH = 26; //rounded up base64url encoded (16 bytes IV + 0 bytes empty string) + file suffix = 26 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 MAX_CIPHER_NAME_LENGTH = 220; // calculations done in https://github.com/cryptomator/cryptofs/issues/60#issuecomment-523238303
+ public static final int MIN_CIPHER_NAME_LENGTH = 28; //rounded up base64url encoded (16 bytes IV + 0 bytes empty string) + file suffix = 28 ASCII chars
+ public static final int MAX_ADDITIONAL_PATH_LENGTH = 48; // beginning at d/... see https://github.com/cryptomator/cryptofs/issues/77
public static final String SEPARATOR = "/";
public static final String RECOVERY_DIR_NAME = "LOST+FOUND";
diff --git a/src/main/java/org/cryptomator/cryptofs/common/FileSystemCapabilityChecker.java b/src/main/java/org/cryptomator/cryptofs/common/FileSystemCapabilityChecker.java
index 7b2cdc47..f976e578 100644
--- a/src/main/java/org/cryptomator/cryptofs/common/FileSystemCapabilityChecker.java
+++ b/src/main/java/org/cryptomator/cryptofs/common/FileSystemCapabilityChecker.java
@@ -7,8 +7,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.inject.Inject;
-import javax.inject.Singleton;
import java.io.IOException;
import java.nio.file.DirectoryIteratorException;
import java.nio.file.DirectoryStream;
@@ -17,13 +15,9 @@
import java.nio.file.Files;
import java.nio.file.Path;
-@Singleton
-public class FileSystemCapabilityChecker {
+public final class FileSystemCapabilityChecker {
private static final Logger LOG = LoggerFactory.getLogger(FileSystemCapabilityChecker.class);
- private static final int MAX_CIPHERTEXT_NAME_LENGTH = 220; // inclusive. calculations done in https://github.com/cryptomator/cryptofs/issues/60#issuecomment-523238303
- private static final int MIN_CIPHERTEXT_NAME_LENGTH = 28; // base64(iv).c9r
- private static final int MAX_ADDITIONAL_PATH_LENGTH = 48; // beginning at d/... see https://github.com/cryptomator/cryptofs/issues/77
public enum Capability {
/**
@@ -41,8 +35,8 @@ public enum Capability {
WRITE_ACCESS,
}
- @Inject
- public FileSystemCapabilityChecker() {
+ private FileSystemCapabilityChecker() {
+
}
/**
@@ -53,7 +47,7 @@ public FileSystemCapabilityChecker() {
* @implNote Only short-running tests with constant time are performed
* @since 1.9.2
*/
- public void assertAllCapabilities(Path pathToVault) throws MissingCapabilityException {
+ public static void assertAllCapabilities(Path pathToVault) throws MissingCapabilityException {
assertReadAccess(pathToVault);
assertWriteAccess(pathToVault);
}
@@ -65,7 +59,7 @@ public void assertAllCapabilities(Path pathToVault) throws MissingCapabilityExce
* @throws MissingCapabilityException if the check fails
* @since 1.9.3
*/
- public void assertReadAccess(Path pathToVault) throws MissingCapabilityException {
+ public static void assertReadAccess(Path pathToVault) throws MissingCapabilityException {
try (DirectoryStream ds = Files.newDirectoryStream(pathToVault)) {
assert ds != null;
} catch (IOException e) {
@@ -80,7 +74,7 @@ public void assertReadAccess(Path pathToVault) throws MissingCapabilityException
* @throws MissingCapabilityException if the check fails
* @since 1.9.3
*/
- public void assertWriteAccess(Path pathToVault) throws MissingCapabilityException {
+ public static void assertWriteAccess(Path pathToVault) throws MissingCapabilityException {
Path checkDir = pathToVault.resolve("c");
try {
Files.createDirectories(checkDir);
@@ -93,9 +87,9 @@ public void assertWriteAccess(Path pathToVault) throws MissingCapabilityExceptio
}
}
- public int determineSupportedCleartextFileNameLength(Path pathToVault) throws IOException {
+ public static int determineSupportedCleartextFileNameLength(Path pathToVault) throws IOException {
int maxCiphertextLen = determineSupportedCiphertextFileNameLength(pathToVault);
- assert maxCiphertextLen >= MIN_CIPHERTEXT_NAME_LENGTH;
+ assert maxCiphertextLen >= Constants.MIN_CIPHER_NAME_LENGTH;
// math explained in https://github.com/cryptomator/cryptofs/issues/60#issuecomment-523238303;
// subtract 4 for file extension, base64-decode, subtract 16 for IV
return (maxCiphertextLen - 4) / 4 * 3 - 16;
@@ -108,22 +102,22 @@ public int determineSupportedCleartextFileNameLength(Path pathToVault) throws IO
* @return Number of chars a .c9r file is allowed to have
* @throws IOException If unable to perform this check
*/
- public int determineSupportedCiphertextFileNameLength(Path pathToVault) throws IOException {
- int subPathLength = MAX_ADDITIONAL_PATH_LENGTH - 2; // subtract "c/"
- return determineSupportedCiphertextFileNameLength(pathToVault.resolve("c"), subPathLength, MIN_CIPHERTEXT_NAME_LENGTH, MAX_CIPHERTEXT_NAME_LENGTH);
+ public static int determineSupportedCiphertextFileNameLength(Path pathToVault) throws IOException {
+ int subPathLength = Constants.MAX_ADDITIONAL_PATH_LENGTH - 2; // subtract "c/"
+ return determineSupportedCiphertextFileNameLength(pathToVault.resolve("c"), subPathLength, Constants.MIN_CIPHER_NAME_LENGTH, Constants.MAX_CIPHER_NAME_LENGTH);
}
/**
* Determines the number of chars a filename is allowed to have inside of subdirectories of dir
by running an experiment.
*
- * @param dir Path to a directory where to conduct the experiment (e.g. /path/to/vault/c
)
- * @param subPathLength Defines the combined number of chars of the subdirectories inside dir
, including slashes but excluding the leading slash. Must be a minimum of 6
+ * @param dir Path to a directory where to conduct the experiment (e.g. /path/to/vault/c
)
+ * @param subPathLength Defines the combined number of chars of the subdirectories inside dir
, including slashes but excluding the leading slash. Must be a minimum of 6
* @param minFileNameLength The minimum filename length to check
* @param maxFileNameLength The maximum filename length to check
* @return The supported filename length inside a subdirectory of dir
with subPathLength
chars
* @throws IOException If unable to perform this check
*/
- public int determineSupportedCiphertextFileNameLength(Path dir, int subPathLength, int minFileNameLength, int maxFileNameLength) throws IOException {
+ public static int determineSupportedCiphertextFileNameLength(Path dir, int subPathLength, int minFileNameLength, int maxFileNameLength) throws IOException {
Preconditions.checkArgument(subPathLength >= 6, "subPathLength must be larger than charcount(a/nnn/)");
Preconditions.checkArgument(minFileNameLength > 0);
Preconditions.checkArgument(maxFileNameLength <= 999);
@@ -144,7 +138,7 @@ public int determineSupportedCiphertextFileNameLength(Path dir, int subPathLengt
}
}
- private int determineSupportedCiphertextFileNameLength(Path p, int lowerBoundIncl, int upperBoundExcl) {
+ private static int determineSupportedCiphertextFileNameLength(Path p, int lowerBoundIncl, int upperBoundExcl) {
assert lowerBoundIncl < upperBoundExcl;
int mid = (lowerBoundIncl + upperBoundExcl) / 2;
assert mid < upperBoundExcl;
@@ -159,7 +153,7 @@ private int determineSupportedCiphertextFileNameLength(Path p, int lowerBoundInc
}
}
- private boolean canHandleFileNameLength(Path parent, int nameLength) {
+ private static boolean canHandleFileNameLength(Path parent, int nameLength) {
Path checkDir = parent.resolve(String.format("%03d", nameLength));
Path checkFile = checkDir.resolve(Strings.repeat("a", nameLength));
try {
@@ -178,7 +172,7 @@ private boolean canHandleFileNameLength(Path parent, int nameLength) {
}
}
- private boolean canListDir(Path dir) {
+ private static boolean canListDir(Path dir) {
try (DirectoryStream ds = Files.newDirectoryStream(dir)) {
ds.iterator().hasNext(); // throws DirectoryIteratorException on Windows if child path too long
return true;
@@ -187,7 +181,7 @@ private boolean canListDir(Path dir) {
}
}
- private void deleteSilently(Path path) {
+ private static void deleteSilently(Path path) {
try {
Files.delete(path);
} catch (IOException e) {
@@ -195,7 +189,7 @@ private void deleteSilently(Path path) {
}
}
- private void deleteRecursivelySilently(Path dir) {
+ private static void deleteRecursivelySilently(Path dir) {
try {
if (Files.exists(dir)) {
MoreFiles.deleteRecursively(dir, RecursiveDeleteOption.ALLOW_INSECURE);
diff --git a/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java b/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java
index 957ad555..4a7db946 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 13f87c11..413601d0 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/fh/OpenCryptoFile.java b/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFile.java
index 14e5d072..ca507291 100644
--- a/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFile.java
+++ b/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFile.java
@@ -1,11 +1,3 @@
-/*******************************************************************************
- * 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.fh;
import org.cryptomator.cryptofs.EffectiveOpenOptions;
@@ -23,8 +15,7 @@
import java.nio.file.attribute.FileTime;
import java.time.Instant;
import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
@@ -35,19 +26,20 @@ public class OpenCryptoFile implements Closeable {
private final FileCloseListener listener;
private final AtomicReference lastModified;
- private final ChunkCache chunkCache;
private final Cryptor cryptor;
private final FileHeaderHolder headerHolder;
private final ChunkIO chunkIO;
private final AtomicReference currentFilePath;
private final AtomicLong fileSize;
private final OpenCryptoFileComponent component;
- private final ConcurrentMap openChannels = new ConcurrentHashMap<>();
+
+ private final AtomicInteger openChannelsCount = new AtomicInteger(0);
@Inject
- public OpenCryptoFile(FileCloseListener listener, ChunkCache chunkCache, Cryptor cryptor, FileHeaderHolder headerHolder, ChunkIO chunkIO, @CurrentOpenFilePath AtomicReference currentFilePath, @OpenFileSize AtomicLong fileSize, @OpenFileModifiedDate AtomicReference lastModified, OpenCryptoFileComponent component) {
+ public OpenCryptoFile(FileCloseListener listener, Cryptor cryptor, FileHeaderHolder headerHolder, ChunkIO chunkIO, //
+ @CurrentOpenFilePath AtomicReference currentFilePath, @OpenFileSize AtomicLong fileSize, //
+ @OpenFileModifiedDate AtomicReference lastModified, OpenCryptoFileComponent component) {
this.listener = listener;
- this.chunkCache = chunkCache;
this.cryptor = cryptor;
this.headerHolder = headerHolder;
this.chunkIO = chunkIO;
@@ -71,30 +63,26 @@ public synchronized FileChannel newFileChannel(EffectiveOpenOptions options, Fil
}
FileChannel ciphertextFileChannel = null;
CleartextFileChannel cleartextFileChannel = null;
+
+ openChannelsCount.incrementAndGet(); // synchronized context, hence we can proactively increase the number
try {
ciphertextFileChannel = path.getFileSystem().provider().newFileChannel(path, options.createOpenOptionsForEncryptedFile(), attrs);
initFileHeader(options, ciphertextFileChannel);
- if (options.truncateExisting()) {
- chunkCache.invalidateStale();
- ciphertextFileChannel.truncate(cryptor.fileHeaderCryptor().headerSize());
- fileSize.set(0);
- }
initFileSize(ciphertextFileChannel);
cleartextFileChannel = component.newChannelComponent() //
- .create(ciphertextFileChannel, options, this::channelClosed) //
+ .create(ciphertextFileChannel, options, this::cleartextChannelClosed) //
.channel();
+ if (options.truncateExisting()) {
+ cleartextFileChannel.truncate(0);
+ }
} finally {
if (cleartextFileChannel == null) { // i.e. something didn't work
+ cleartextChannelClosed(ciphertextFileChannel);
closeQuietly(ciphertextFileChannel);
- // is this the first file channel to be opened?
- if (openChannels.isEmpty()) {
- close(); // then also close the file again.
- }
}
}
assert cleartextFileChannel != null; // otherwise there would have been an exception
- openChannels.put(cleartextFileChannel, ciphertextFileChannel);
chunkIO.registerChannel(ciphertextFileChannel, options.writable());
return cleartextFileChannel;
}
@@ -183,12 +171,11 @@ public void updateCurrentFilePath(Path newFilePath) {
currentFilePath.updateAndGet(p -> p == null ? null : newFilePath);
}
- private synchronized void channelClosed(CleartextFileChannel cleartextFileChannel) {
- FileChannel ciphertextFileChannel = openChannels.remove(cleartextFileChannel);
+ private synchronized void cleartextChannelClosed(FileChannel ciphertextFileChannel) {
if (ciphertextFileChannel != null) {
chunkIO.unregisterChannel(ciphertextFileChannel);
}
- if (openChannels.isEmpty()) {
+ if (openChannelsCount.decrementAndGet() == 0) {
close();
}
}
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 f283d312..f553a9f5 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 28cd3614..0f9df968 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 9002ba94..9512d5e6 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 c6b019b0..de6edbaa 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 350616a9..d8907406 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/main/java/org/cryptomator/cryptofs/migration/MigrationModule.java b/src/main/java/org/cryptomator/cryptofs/migration/MigrationModule.java
index fcc9ba7e..4025765e 100644
--- a/src/main/java/org/cryptomator/cryptofs/migration/MigrationModule.java
+++ b/src/main/java/org/cryptomator/cryptofs/migration/MigrationModule.java
@@ -25,11 +25,6 @@
@Module
class MigrationModule {
- @Provides
- FileSystemCapabilityChecker provideFileSystemCapabilityChecker() {
- return new FileSystemCapabilityChecker();
- }
-
@Provides
@IntoMap
@MigratorKey(Migration.FIVE_TO_SIX)
diff --git a/src/main/java/org/cryptomator/cryptofs/migration/Migrators.java b/src/main/java/org/cryptomator/cryptofs/migration/Migrators.java
index 09a43b95..3f891af2 100644
--- a/src/main/java/org/cryptomator/cryptofs/migration/Migrators.java
+++ b/src/main/java/org/cryptomator/cryptofs/migration/Migrators.java
@@ -46,12 +46,10 @@ public class Migrators {
private static final MigrationComponent COMPONENT = DaggerMigrationComponent.builder().csprng(strongSecureRandom()).build();
private final Map migrators;
- private final FileSystemCapabilityChecker fsCapabilityChecker;
@Inject
- Migrators(Map migrators, FileSystemCapabilityChecker fsCapabilityChecker) {
+ Migrators(Map migrators) {
this.migrators = migrators;
- this.fsCapabilityChecker = fsCapabilityChecker;
}
private static SecureRandom strongSecureRandom() {
@@ -69,9 +67,9 @@ public static Migrators get() {
/**
* Inspects the vault and checks if it is supported by this library.
*
- * @param pathToVault Path to the vault's root
+ * @param pathToVault Path to the vault's root
* @param vaultConfigFilename Name of the vault config file located in the vault
- * @param masterkeyFilename Name of the masterkey file optionally located in the vault
+ * @param masterkeyFilename Name of the masterkey file optionally located in the vault
* @return true
if the vault at the given path is of an older format than supported by this library
* @throws IOException if an I/O error occurs parsing the masterkey file
*/
@@ -83,19 +81,19 @@ public boolean needsMigration(Path pathToVault, String vaultConfigFilename, Stri
/**
* Performs the actual migration. This task may take a while and this method will block.
*
- * @param pathToVault Path to the vault's root
- * @param vaultConfigFilename Name of the vault config file located inside pathToVault
- * @param masterkeyFilename Name of the masterkey file located inside pathToVault
- * @param passphrase The passphrase needed to unlock the vault
- * @param progressListener Listener that will get notified of progress updates
+ * @param pathToVault Path to the vault's root
+ * @param vaultConfigFilename Name of the vault config file located inside pathToVault
+ * @param masterkeyFilename Name of the masterkey file located inside pathToVault
+ * @param passphrase The passphrase needed to unlock the vault
+ * @param progressListener Listener that will get notified of progress updates
* @param continuationListener Listener that will get asked if there are events that require feedback
- * @throws NoApplicableMigratorException If the vault can not be migrated, because no migrator could be found
- * @throws InvalidPassphraseException If the passphrase could not be used to unlock the vault
+ * @throws NoApplicableMigratorException If the vault can not be migrated, because no migrator could be found
+ * @throws InvalidPassphraseException If the passphrase could not be used to unlock the vault
* @throws FileSystemCapabilityChecker.MissingCapabilityException If the underlying filesystem lacks features required to store a vault
- * @throws IOException if an I/O error occurs migrating the vault
+ * @throws IOException if an I/O error occurs migrating the vault
*/
public void migrate(Path pathToVault, String vaultConfigFilename, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener, MigrationContinuationListener continuationListener) throws NoApplicableMigratorException, CryptoException, IOException {
- fsCapabilityChecker.assertAllCapabilities(pathToVault);
+ FileSystemCapabilityChecker.assertAllCapabilities(pathToVault);
int vaultVersion = determineVaultVersion(pathToVault, vaultConfigFilename, masterkeyFilename);
try {
Migrator migrator = findApplicableMigrator(vaultVersion).orElseThrow(NoApplicableMigratorException::new);
diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java b/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java
index 86a3fdb8..510c4d99 100644
--- a/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java
+++ b/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java
@@ -66,7 +66,7 @@ public void migrate(Path vaultRoot, String vaultConfigFilename, String masterkey
LOG.info("Backed up masterkey from {} to {}.", masterkeyFile.getFileName(), masterkeyBackupFile.getFileName());
// check file system capabilities:
- int filenameLengthLimit = new FileSystemCapabilityChecker().determineSupportedCiphertextFileNameLength(vaultRoot.resolve("c"), 46, 28, 220);
+ int filenameLengthLimit = FileSystemCapabilityChecker.determineSupportedCiphertextFileNameLength(vaultRoot.resolve("c"), 46, 28, 220);
int pathLengthLimit = filenameLengthLimit + 48; // TODO
PreMigrationVisitor preMigrationVisitor;
if (filenameLengthLimit >= 220) {
diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java
index 2bcbaf42..949ae7fc 100644
--- a/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java
@@ -50,6 +50,8 @@
import java.util.List;
import java.util.Random;
import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -617,11 +619,13 @@ public void testWriteThenDeleteThenRead() throws IOException {
}
@RepeatedTest(50)
- public void testConcurrentWriteAndTruncate() throws IOException {
+ public void testConcurrentWriteAndTruncate() throws IOException, InterruptedException {
AtomicBoolean keepWriting = new AtomicBoolean(true);
- ByteBuffer buf = ByteBuffer.wrap("the quick brown fox jumps over the lazy dog".getBytes(StandardCharsets.UTF_8));
- var executor = Executors.newCachedThreadPool();
- try (FileChannel writingChannel = FileChannel.open(file, WRITE, CREATE)) {
+ ByteBuffer buf = ByteBuffer.allocate(50_000); // 50 kiB
+
+ try (ExecutorService executor = Executors.newCachedThreadPool();
+ FileChannel writingChannel = FileChannel.open(file, WRITE, CREATE)) {
+ var cdl = new CountDownLatch(3);
executor.submit(() -> {
while (keepWriting.get()) {
try {
@@ -630,20 +634,22 @@ public void testConcurrentWriteAndTruncate() throws IOException {
throw new UncheckedIOException(e);
}
buf.flip();
+ cdl.countDown();
}
});
+ cdl.await();
try (FileChannel truncatingChannel = FileChannel.open(file, WRITE, TRUNCATE_EXISTING)) {
keepWriting.set(false);
}
- executor.shutdown();
}
- Assertions.assertDoesNotThrow(() -> {
- try (FileChannel readingChannel = FileChannel.open(file, READ)) {
- var dst = ByteBuffer.allocate(buf.capacity());
+
+ try (FileChannel readingChannel = FileChannel.open(file, READ)) {
+ var dst = ByteBuffer.allocate(buf.capacity());
+ Assertions.assertDoesNotThrow(() -> {
readingChannel.read(dst);
- }
- });
+ });
+ }
}
}
diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java
index 3b7d8462..5343dfc7 100644
--- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java
@@ -105,6 +105,7 @@ public class CryptoFileSystemImplTest {
private final CiphertextDirectoryDeleter ciphertextDirDeleter = mock(CiphertextDirectoryDeleter.class);
private final ReadonlyFlag readonlyFlag = mock(ReadonlyFlag.class);
private final CryptoFileSystemProperties fileSystemProperties = mock(CryptoFileSystemProperties.class);
+ private final FileNameDecryptor filenameDecryptor = mock(FileNameDecryptor.class);
private final CryptoPath root = mock(CryptoPath.class);
private final CryptoPath empty = mock(CryptoPath.class);
@@ -127,7 +128,7 @@ public void setup() {
pathMatcherFactory, directoryStreamFactory, dirIdProvider, dirIdBackup, //
fileAttributeProvider, fileAttributeByNameProvider, fileAttributeViewProvider, //
openCryptoFiles, symlinks, finallyUtil, ciphertextDirDeleter, readonlyFlag, //
- fileSystemProperties);
+ fileSystemProperties, filenameDecryptor);
}
@Test
@@ -1277,7 +1278,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 a1dc036d..ab8f8bea 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/CryptoFileSystemsTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java
index df120142..7e646e57 100644
--- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java
@@ -41,7 +41,6 @@ public class CryptoFileSystemsTest {
private final Path dataDirPath = mock(Path.class, "normalizedVaultPath/d");
private final Path preContenRootPath = mock(Path.class, "normalizedVaultPath/d/AB");
private final Path contenRootPath = mock(Path.class, "normalizedVaultPath/d/AB/CDEFGHIJKLMNOP");
- private final FileSystemCapabilityChecker capabilityChecker = mock(FileSystemCapabilityChecker.class);
private final CryptoFileSystemProvider provider = mock(CryptoFileSystemProvider.class);
private final CryptoFileSystemProperties properties = mock(CryptoFileSystemProperties.class);
private final CryptoFileSystemComponent cryptoFileSystemComponent = mock(CryptoFileSystemComponent.class);
@@ -65,7 +64,7 @@ public class CryptoFileSystemsTest {
private MockedStatic cryptorProviderClass;
private MockedStatic backupHelperClass;
- private final CryptoFileSystems inTest = new CryptoFileSystems(cryptoFileSystemComponentFactory, capabilityChecker, csprng);
+ private final CryptoFileSystems inTest = new CryptoFileSystems(cryptoFileSystemComponentFactory, csprng);
@BeforeEach
public void setup() throws IOException, MasterkeyLoadingFailedException {
diff --git a/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java
index 12e090a2..713dc7f9 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 58739e90..fb120e4b 100644
--- a/src/test/java/org/cryptomator/cryptofs/DirectoryIdBackupTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/DirectoryIdBackupTest.java
@@ -1,13 +1,18 @@
package org.cryptomator.cryptofs;
import org.cryptomator.cryptofs.common.Constants;
+import org.cryptomator.cryptofs.health.dirid.OrphanContentDirTest;
+import org.cryptomator.cryptofs.util.TestCryptoException;
+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 +20,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 +31,6 @@ public class DirectoryIdBackupTest {
Path contentPath;
private String dirId = "12345678";
- private CiphertextDirectory ciphertextDirectoryObject;
- private EncryptingWritableByteChannel encChannel;
private Cryptor cryptor;
private DirectoryIdBackup dirIdBackup;
@@ -31,37 +38,108 @@ 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);
+
+ dirIdBackupSpy.write(ciphertextDirectoryObject);
+
+ 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));
- dirIdBackup.execute(ciphertextDirectoryObject);
+ dirIdBackupSpy.write(ciphertextDirectoryObject);
- Assertions.assertTrue(Files.exists(contentPath.resolve(Constants.DIR_BACKUP_FILE_NAME)));
+ 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 TestCryptoException();
+ 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);
+ }
+
+ @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/FileNameDecryptorTest.java b/src/test/java/org/cryptomator/cryptofs/FileNameDecryptorTest.java
new file mode 100644
index 00000000..c5874d8b
--- /dev/null
+++ b/src/test/java/org/cryptomator/cryptofs/FileNameDecryptorTest.java
@@ -0,0 +1,209 @@
+package org.cryptomator.cryptofs;
+
+import org.cryptomator.cryptofs.common.Constants;
+import org.cryptomator.cryptofs.util.TestCryptoException;
+import org.cryptomator.cryptolib.api.Cryptor;
+import org.cryptomator.cryptolib.api.FileNameCryptor;
+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.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.Mockito;
+
+import java.io.IOException;
+import java.nio.file.FileSystemException;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class FileNameDecryptorTest {
+
+ @TempDir
+ Path tmpPath;
+ Path vaultPath = mock(Path.class);
+ DirectoryIdBackup dirIdBackup = mock(DirectoryIdBackup.class);
+ LongFileNameProvider longFileNameProvider = mock(LongFileNameProvider.class);
+ FileNameCryptor fileNameCryptor = mock(FileNameCryptor.class);
+ FileNameDecryptor testObj;
+ FileNameDecryptor testObjSpy;
+
+ @BeforeEach
+ public void beforeEach() {
+ var cryptor = mock(Cryptor.class);
+ when(cryptor.fileNameCryptor()).thenReturn(fileNameCryptor);
+ testObj = new FileNameDecryptor(vaultPath, cryptor, dirIdBackup, longFileNameProvider);
+ testObjSpy = Mockito.spy(testObj);
+ }
+
+ @ParameterizedTest
+ @DisplayName("Given a ciphertextNode, it's clearname is returned")
+ @ValueSource(strings = {Constants.DEFLATED_FILE_SUFFIX, Constants.CRYPTOMATOR_FILE_SUFFIX})
+ public void success(String fileExtension) throws IOException {
+ var ciphertextNodeNameName = "someFile";
+ var ciphertextNode = tmpPath.resolve(ciphertextNodeNameName + fileExtension);
+ var dirId = new byte[]{'f', 'o', 'o', 'b', 'a', 'r'};
+ var expectedClearName = "veryClearText";
+ when(dirIdBackup.read(ciphertextNode)).thenReturn(dirId);
+ when(longFileNameProvider.inflate(ciphertextNode)).thenReturn(ciphertextNodeNameName);
+ when(fileNameCryptor.decryptFilename(any(), eq(ciphertextNodeNameName), eq(dirId))).thenReturn(expectedClearName);
+
+ var result = testObjSpy.decryptFilenameInternal(ciphertextNode);
+ verify(fileNameCryptor).decryptFilename(any(), eq(ciphertextNodeNameName), eq(dirId));
+ Assertions.assertEquals(expectedClearName, result);
+ }
+
+ @Test
+ @DisplayName("Path is validated before computation")
+ public void validatePath() throws IOException {
+ var ciphertextNode = tmpPath.resolve("someFile.c9r");
+ Mockito.doNothing().when(testObjSpy).validatePath(any());
+ Mockito.doReturn("veryClearName").when(testObjSpy).decryptFilenameInternal(any());
+
+ var actual = testObjSpy.decryptFilename(ciphertextNode);
+ Assertions.assertEquals("veryClearName", actual);
+ }
+
+ @Test
+ @DisplayName("If the dirId backup file does not exists, throw UnsupportedOperationException")
+ public void notExistingDirIdFile() throws IOException {
+ var ciphertextNode = tmpPath.resolve("toDecrypt.c9r");
+ when(dirIdBackup.read(ciphertextNode)).thenThrow(NoSuchFileException.class);
+
+ Assertions.assertThrows(UnsupportedOperationException.class, () -> testObjSpy.decryptFilenameInternal(ciphertextNode));
+ }
+
+ @Test
+ @DisplayName("If the dirId cannot be read, throw FileSystemException")
+ public void notReadableDirIdFile() throws IOException {
+ var ciphertextNode = tmpPath.resolve("toDecrypt.c9r");
+ when(dirIdBackup.read(ciphertextNode)) //
+ .thenThrow(TestCryptoException.class) //
+ .thenThrow(IllegalStateException.class);
+ Assertions.assertThrows(FileSystemException.class, () -> testObjSpy.decryptFilenameInternal(ciphertextNode));
+ Assertions.assertThrows(FileSystemException.class, () -> testObjSpy.decryptFilenameInternal(ciphertextNode));
+ }
+
+ @Test
+ @DisplayName("If the ciphertextName cannot be decrypted, throw FileSystemException")
+ public void notDecryptableCiphertext() throws IOException {
+ var name = "toDecrypt";
+ var ciphertextNode = tmpPath.resolve(name + ".c9s");
+ var dirId = new byte[]{'f', 'o', 'o', 'b', 'a', 'r'};
+ var expectedException = new IOException("Inflation failed");
+ when(dirIdBackup.read(ciphertextNode)).thenReturn(dirId);
+ when(longFileNameProvider.inflate(ciphertextNode)).thenThrow(expectedException);
+
+ var actual = Assertions.assertThrows(IOException.class, () -> testObjSpy.decryptFilenameInternal(ciphertextNode));
+ Assertions.assertEquals(expectedException, actual);
+ }
+
+ @Test
+ @DisplayName("If inflating the shortened Name throws exception, it is rethrown")
+ public void inflateThrows() throws IOException {
+ var name = "toDecrypt";
+ var ciphertextNode = tmpPath.resolve(name + ".c9r");
+ var dirId = new byte[]{'f', 'o', 'o', 'b', 'a', 'r'};
+ when(dirIdBackup.read(ciphertextNode)).thenReturn(dirId);
+ when(fileNameCryptor.decryptFilename(any(), eq(name), eq(dirId))).thenThrow(TestCryptoException.class);
+
+ Assertions.assertThrows(FileSystemException.class, () -> testObjSpy.decryptFilenameInternal(ciphertextNode));
+ verify(fileNameCryptor).decryptFilename(any(), eq(name), eq(dirId));
+ }
+
+ @Nested
+ public class TestValidation {
+
+ Path p = mock(Path.class, "/absolute/path/to/ciphertext.c9r");
+
+ @BeforeEach
+ public void beforeEach() {
+ doReturn(true).when(testObjSpy).belongsToVault(p);
+ doReturn(true).when(testObjSpy).isAtCipherNodeLevel(p);
+ doReturn(true).when(testObjSpy).hasCipherNodeExtension(p);
+ doReturn(true).when(testObjSpy).hasMinimumFileNameLength(p);
+ }
+
+ @Test
+ @DisplayName("If node is not part of the vault, validation fails")
+ public void validateNotVaultFile() {
+ doReturn(false).when(testObjSpy).belongsToVault(p);
+ Assertions.assertThrows(IllegalArgumentException.class, () -> testObjSpy.validatePath(p));
+ verify(testObjSpy).belongsToVault(any());
+ }
+
+ @Test
+ @DisplayName("If node is on the wrong level, validation fails")
+ public void validateWrongLevel() {
+ doReturn(false).when(testObjSpy).isAtCipherNodeLevel(p);
+ Assertions.assertThrows(IllegalArgumentException.class, () -> testObjSpy.validatePath(p));
+ verify(testObjSpy).isAtCipherNodeLevel(any());
+ }
+
+
+ @Test
+ @DisplayName("If node has wrong file extension, validation fails")
+ public void validateWrongExtension() {
+ doReturn(false).when(testObjSpy).hasCipherNodeExtension(p);
+ Assertions.assertThrows(IllegalArgumentException.class, () -> testObjSpy.validatePath(p));
+ verify(testObjSpy).hasCipherNodeExtension(any());
+ }
+
+ @Test
+ @DisplayName("If filename is too short, validation fails")
+ public void validateTooShort() {
+ doReturn(false).when(testObjSpy).hasMinimumFileNameLength(p);
+ Assertions.assertThrows(IllegalArgumentException.class, () -> testObjSpy.validatePath(p));
+ verify(testObjSpy).hasMinimumFileNameLength(any());
+ }
+ }
+
+ @Nested
+ public class IsAtCipherNodeLevel {
+
+ @TempDir
+ Path tmpDir;
+
+ @Test
+ @DisplayName("cipherNodeLevel test requires an absolute path")
+ public void requiresAbsolutePath() {
+ var relativePath = Path.of("relative/path");
+ Assertions.assertThrows(IllegalArgumentException.class, () -> testObj.isAtCipherNodeLevel(relativePath));
+ }
+
+ @Test
+ public void success() {
+ when(vaultPath.getNameCount()).thenReturn(tmpDir.getNameCount());
+ var p = tmpDir.resolve("d/AA/BBBBBBBBBBBBBBB/encrypted.file");
+ Assertions.assertTrue(testObj.isAtCipherNodeLevel(p));
+ }
+
+ @Test
+ public void failure() {
+ when(vaultPath.getNameCount()).thenReturn(tmpDir.getNameCount());
+ var p = tmpDir.resolve("d/AA/other.file");
+ Assertions.assertFalse(testObj.isAtCipherNodeLevel(p));
+ }
+ }
+
+ @ParameterizedTest
+ @DisplayName("Only c9r and c9s are accepted file extensions")
+ @CsvSource(value = {"file.c9r,true", "file.c9s,true", "filec9r,false", "file.c9l,false",})
+ public void testHasCipherNodeExtension(String filename, boolean expected) {
+ var p = Path.of(filename);
+ var result = testObj.hasCipherNodeExtension(p);
+ Assertions.assertEquals(expected, result, "The filename %s is WRONGLY %s".formatted(filename, result ? "accepted" : "rejected"));
+ }
+
+
+}
diff --git a/src/test/java/org/cryptomator/cryptofs/ch/CleartextFileChannelTest.java b/src/test/java/org/cryptomator/cryptofs/ch/CleartextFileChannelTest.java
index a19e508d..e203b16b 100644
--- a/src/test/java/org/cryptomator/cryptofs/ch/CleartextFileChannelTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/ch/CleartextFileChannelTest.java
@@ -43,6 +43,7 @@
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
+import java.util.function.Consumer;
import static org.hamcrest.CoreMatchers.is;
import static org.mockito.ArgumentMatchers.any;
@@ -76,7 +77,7 @@ public class CleartextFileChannelTest {
private AtomicReference lastModified = new AtomicReference<>(Instant.ofEpochMilli(0));
private BasicFileAttributeView attributeView = mock(BasicFileAttributeView.class);
private ExceptionsDuringWrite exceptionsDuringWrite = mock(ExceptionsDuringWrite.class);
- private ChannelCloseListener closeListener = mock(ChannelCloseListener.class);
+ private Consumer closeListener = mock(Consumer.class);
private CryptoFileSystemStats stats = mock(CryptoFileSystemStats.class);
private CleartextFileChannel inTest;
@@ -242,11 +243,22 @@ public void testCloseIoExceptionFlush() throws IOException {
Assertions.assertThrows(IOException.class, () -> inSpy.implCloseChannel());
- verify(closeListener).closed(inSpy);
+ verify(closeListener).accept(ciphertextFileChannel);
verify(ciphertextFileChannel).close();
verify(inSpy).persistLastModified();
}
+ @Test
+ @DisplayName("On close, first flush channel, then unregister")
+ public void testCloseCipherChannelFlushBeforeUnregister() throws IOException {
+ var inSpy = spy(inTest);
+ inSpy.implCloseChannel();
+
+ var ordering = inOrder(inSpy, closeListener);
+ ordering.verify(inSpy).flush();
+ verify(closeListener).accept(ciphertextFileChannel);
+ }
+
@Test
@DisplayName("On close, first close channel, then persist lastModified")
public void testCloseCipherChannelCloseBeforePersist() throws IOException {
@@ -278,8 +290,8 @@ public void testCloseExceptionOnLastModifiedPersistenceIgnored() throws IOExcept
var inSpy = Mockito.spy(inTest);
Mockito.doThrow(IOException.class).when(inSpy).persistLastModified();
- Assertions.assertDoesNotThrow(() -> inSpy.implCloseChannel());
- verify(closeListener).closed(inSpy);
+ Assertions.assertDoesNotThrow(inSpy::implCloseChannel);
+ verify(closeListener).accept(ciphertextFileChannel);
verify(ciphertextFileChannel).close();
}
diff --git a/src/test/java/org/cryptomator/cryptofs/common/FileSystemCapabilityCheckerTest.java b/src/test/java/org/cryptomator/cryptofs/common/FileSystemCapabilityCheckerTest.java
index 1c363eaf..4bb1ccb6 100644
--- a/src/test/java/org/cryptomator/cryptofs/common/FileSystemCapabilityCheckerTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/common/FileSystemCapabilityCheckerTest.java
@@ -20,18 +20,18 @@
import java.util.Collections;
public class FileSystemCapabilityCheckerTest {
-
+
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class PathLengthLimits {
-
+
private Path pathToVault = Mockito.mock(Path.class);
private Path cDir = Mockito.mock(Path.class);
private Path fillerDir = Mockito.mock(Path.class);
private Path nnnDir = Mockito.mock(Path.class);
private FileSystem fileSystem = Mockito.mock(FileSystem.class);
private FileSystemProvider fileSystemProvider = Mockito.mock(FileSystemProvider.class);
-
+
@BeforeEach
public void setup() throws IOException {
Mockito.when(pathToVault.getFileSystem()).thenReturn(fileSystem);
@@ -55,15 +55,14 @@ public void testUnlimitedLength() throws IOException {
String checkFileStr = invocation.getArgument(0);
Path checkFileMock = Mockito.mock(Path.class, checkFileStr);
Mockito.when(checkFileMock.getFileSystem()).thenReturn(fileSystem);
- Mockito.when(fileSystemProvider.newByteChannel(Mockito.eq(checkFileMock), Mockito.any()))
- .thenReturn(new SeekableByteChannelMock(0));
+ Mockito.when(fileSystemProvider.newByteChannel(Mockito.eq(checkFileMock), Mockito.any())).thenReturn(new SeekableByteChannelMock(0));
return checkFileMock;
});
Mockito.when(fileSystemProvider.newDirectoryStream(Mockito.eq(checkDirMock), Mockito.any())).thenReturn(DirectoryStreamMock.empty());
return checkDirMock;
});
- int determinedLimit = new FileSystemCapabilityChecker().determineSupportedCiphertextFileNameLength(pathToVault);
+ int determinedLimit = FileSystemCapabilityChecker.determineSupportedCiphertextFileNameLength(pathToVault);
Assertions.assertEquals(220, determinedLimit);
}
@@ -80,13 +79,12 @@ public void testLimitedLengthDuringDirListing() throws IOException {
String checkFileStr = invocation.getArgument(0);
Path checkFileMock = Mockito.mock(Path.class, checkFileStr);
Mockito.when(checkFileMock.getFileSystem()).thenReturn(fileSystem);
- Mockito.when(fileSystemProvider.newByteChannel(Mockito.eq(checkFileMock), Mockito.any()))
- .thenReturn(new SeekableByteChannelMock(0));
+ Mockito.when(fileSystemProvider.newByteChannel(Mockito.eq(checkFileMock), Mockito.any())).thenReturn(new SeekableByteChannelMock(0));
return checkFileMock;
});
Mockito.when(fileSystemProvider.newDirectoryStream(Mockito.eq(checkDirMock), Mockito.any())).then(invocation3 -> {
Iterable iterable = Mockito.mock(Iterable.class);
- if (Integer.valueOf(checkDirStr) > limit) {
+ if (Integer.parseInt(checkDirStr) > limit) {
Mockito.when(iterable.iterator()).thenThrow(new DirectoryIteratorException(new IOException("path too long")));
} else {
Mockito.when(iterable.iterator()).thenReturn(Collections.emptyIterator());
@@ -96,8 +94,8 @@ public void testLimitedLengthDuringDirListing() throws IOException {
return checkDirMock;
});
- int determinedLimit = new FileSystemCapabilityChecker().determineSupportedCiphertextFileNameLength(pathToVault);
-
+ int determinedLimit = FileSystemCapabilityChecker.determineSupportedCiphertextFileNameLength(pathToVault);
+
Assertions.assertEquals(limit, determinedLimit);
}
@@ -113,12 +111,10 @@ public void testLimitedLengthDuringFileCreation() throws IOException {
String checkFileStr = invocation.getArgument(0);
Path checkFileMock = Mockito.mock(Path.class, checkFileStr);
Mockito.when(checkFileMock.getFileSystem()).thenReturn(fileSystem);
- if (Integer.valueOf(checkDirStr) > limit) {
- Mockito.when(fileSystemProvider.newByteChannel(Mockito.eq(checkFileMock), Mockito.any()))
- .thenThrow(new IOException("name too long"));
+ if (Integer.parseInt(checkDirStr) > limit) {
+ Mockito.when(fileSystemProvider.newByteChannel(Mockito.eq(checkFileMock), Mockito.any())).thenThrow(new IOException("name too long"));
} else {
- Mockito.when(fileSystemProvider.newByteChannel(Mockito.eq(checkFileMock), Mockito.any()))
- .thenReturn(new SeekableByteChannelMock(0));
+ Mockito.when(fileSystemProvider.newByteChannel(Mockito.eq(checkFileMock), Mockito.any())).thenReturn(new SeekableByteChannelMock(0));
}
return checkFileMock;
});
@@ -126,7 +122,7 @@ public void testLimitedLengthDuringFileCreation() throws IOException {
return checkDirMock;
});
- int determinedLimit = new FileSystemCapabilityChecker().determineSupportedCiphertextFileNameLength(pathToVault);
+ int determinedLimit = FileSystemCapabilityChecker.determineSupportedCiphertextFileNameLength(pathToVault);
Assertions.assertEquals(limit, determinedLimit);
}
@@ -136,14 +132,14 @@ public void testLimitedLengthDuringFileCreation() throws IOException {
@CsvSource({"220, 146", "219, 143", "218, 143", "217, 143", "216, 143", "215, 140"})
public void testDetermineSupportedCleartextFileNameLength(int ciphertextLimit, int expectedCleartextLimit) throws IOException {
Path path = Mockito.mock(Path.class);
- FileSystemCapabilityChecker checker = Mockito.spy(new FileSystemCapabilityChecker());
- Mockito.doReturn(ciphertextLimit).when(checker).determineSupportedCiphertextFileNameLength(path);
-
- int result = checker.determineSupportedCleartextFileNameLength(path);
-
- Assertions.assertEquals(expectedCleartextLimit, result);
+ try (var staticCheckerMock = Mockito.mockStatic(FileSystemCapabilityChecker.class)) {
+ staticCheckerMock.when(() -> FileSystemCapabilityChecker.determineSupportedCiphertextFileNameLength(path)).thenReturn(ciphertextLimit);
+ staticCheckerMock.when(() -> FileSystemCapabilityChecker.determineSupportedCleartextFileNameLength(path)).thenCallRealMethod();
+ int result = FileSystemCapabilityChecker.determineSupportedCleartextFileNameLength(path);
+ Assertions.assertEquals(expectedCleartextLimit, result);
+ }
}
-
+
}
}
\ No newline at end of file
diff --git a/src/test/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactoryTest.java b/src/test/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactoryTest.java
index eada83a8..b9fce578 100644
--- a/src/test/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactoryTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactoryTest.java
@@ -3,6 +3,7 @@
import org.cryptomator.cryptofs.CiphertextDirectory;
import org.cryptomator.cryptofs.CryptoPath;
import org.cryptomator.cryptofs.CryptoPathMapper;
+import org.cryptomator.cryptofs.common.Constants;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
@@ -118,10 +119,10 @@ public void testCiphertextDirStreamFilter(String fileName, boolean expected) {
private static Stream provideFilterExamples() {
return Stream.of( //
- Arguments.of("foo25____25chars_____.c9r", false), //
- Arguments.of("bar25____25chars_____.c9s", false), //
- Arguments.of("foo26____26chars______.c9r", true), //
- Arguments.of("bar26____26chars______.c9s", true));
+ Arguments.of("b".repeat(Constants.MIN_CIPHER_NAME_LENGTH - 5)+".c9r", false), //
+ Arguments.of("b".repeat(Constants.MIN_CIPHER_NAME_LENGTH - 5)+".c9s", false), //
+ Arguments.of("a".repeat(Constants.MIN_CIPHER_NAME_LENGTH - 4)+".c9r", true), //
+ Arguments.of("a".repeat(Constants.MIN_CIPHER_NAME_LENGTH - 4)+".c9s", true));
}
}
diff --git a/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFileTest.java b/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFileTest.java
index 3de6a2f9..e50b7d57 100644
--- a/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFileTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFileTest.java
@@ -4,7 +4,6 @@
import com.google.common.jimfs.Jimfs;
import org.cryptomator.cryptofs.EffectiveOpenOptions;
import org.cryptomator.cryptofs.ReadonlyFlag;
-import org.cryptomator.cryptofs.ch.ChannelCloseListener;
import org.cryptomator.cryptofs.ch.ChannelComponent;
import org.cryptomator.cryptofs.ch.CleartextFileChannel;
import org.cryptomator.cryptolib.api.Cryptor;
@@ -27,7 +26,6 @@
import java.io.UncheckedIOException;
import java.nio.channels.FileChannel;
import java.nio.file.FileSystem;
-import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.PosixFilePermissions;
@@ -35,6 +33,7 @@
import java.util.EnumSet;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
@@ -48,7 +47,6 @@ public class OpenCryptoFileTest {
private static AtomicReference CURRENT_FILE_PATH;
private ReadonlyFlag readonlyFlag = mock(ReadonlyFlag.class);
private FileCloseListener closeListener = mock(FileCloseListener.class);
- private ChunkCache chunkCache = mock(ChunkCache.class);
private Cryptor cryptor = mock(Cryptor.class);
private FileHeaderCryptor fileHeaderCryptor = mock(FileHeaderCryptor.class);
private FileHeaderHolder headerHolder = mock(FileHeaderHolder.class);
@@ -72,7 +70,7 @@ public static void tearDown() throws IOException {
@Test
public void testCloseTriggersCloseListener() {
- OpenCryptoFile openCryptoFile = new OpenCryptoFile(closeListener, chunkCache, cryptor, headerHolder, chunkIO, CURRENT_FILE_PATH, fileSize, lastModified, openCryptoFileComponent);
+ OpenCryptoFile openCryptoFile = new OpenCryptoFile(closeListener, cryptor, headerHolder, chunkIO, CURRENT_FILE_PATH, fileSize, lastModified, openCryptoFileComponent);
openCryptoFile.close();
verify(closeListener).close(CURRENT_FILE_PATH.get(), openCryptoFile);
}
@@ -83,7 +81,7 @@ public void testCloseImmediatelyIfOpeningFirstChannelFails() {
UncheckedIOException expectedException = new UncheckedIOException(new IOException("fail!"));
EffectiveOpenOptions options = Mockito.mock(EffectiveOpenOptions.class);
Mockito.when(options.createOpenOptionsForEncryptedFile()).thenThrow(expectedException);
- OpenCryptoFile openCryptoFile = new OpenCryptoFile(closeListener, chunkCache, cryptor, headerHolder, chunkIO, CURRENT_FILE_PATH, fileSize, lastModified, openCryptoFileComponent);
+ OpenCryptoFile openCryptoFile = new OpenCryptoFile(closeListener, cryptor, headerHolder, chunkIO, CURRENT_FILE_PATH, fileSize, lastModified, openCryptoFileComponent);
UncheckedIOException exception = Assertions.assertThrows(UncheckedIOException.class, () -> {
openCryptoFile.newFileChannel(options);
@@ -93,19 +91,20 @@ public void testCloseImmediatelyIfOpeningFirstChannelFails() {
}
@Test
- @DisplayName("Opening a file channel with TRUNCATE_EXISTING sets the file size to 0")
- public void testFileSizeZerodOnTruncateExisting() throws IOException {
+ @DisplayName("Opening a file channel with TRUNCATE_EXISTING calls truncate(0) on the cleartextChannel")
+ public void testCleartextChannelTruncateCalledOnTruncateExisting() throws IOException {
EffectiveOpenOptions options = EffectiveOpenOptions.from(EnumSet.of(StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING), readonlyFlag);
+ var cleartextChannel = mock(CleartextFileChannel.class);
Mockito.when(headerHolder.get()).thenReturn(Mockito.mock(FileHeader.class));
Mockito.when(cryptor.fileHeaderCryptor()).thenReturn(fileHeaderCryptor);
Mockito.when(fileHeaderCryptor.headerSize()).thenReturn(42);
Mockito.when(openCryptoFileComponent.newChannelComponent()).thenReturn(channelComponentFactory);
Mockito.when(channelComponentFactory.create(any(), any(), any())).thenReturn(channelComponent);
- Mockito.when(channelComponent.channel()).thenReturn(mock(CleartextFileChannel.class));
- OpenCryptoFile openCryptoFile = new OpenCryptoFile(closeListener, chunkCache, cryptor, headerHolder, chunkIO, CURRENT_FILE_PATH, fileSize, lastModified, openCryptoFileComponent);
+ Mockito.when(channelComponent.channel()).thenReturn(cleartextChannel);
+ OpenCryptoFile openCryptoFile = new OpenCryptoFile(closeListener, cryptor, headerHolder, chunkIO, CURRENT_FILE_PATH, fileSize, lastModified, openCryptoFileComponent);
openCryptoFile.newFileChannel(options);
- verify(fileSize).set(0L);
+ verify(cleartextChannel).truncate(0L);
}
@Nested
@@ -114,7 +113,7 @@ public class InitFilHeaderTests {
EffectiveOpenOptions options = Mockito.mock(EffectiveOpenOptions.class);
FileChannel cipherFileChannel = Mockito.mock(FileChannel.class, "cipherFilechannel");
- OpenCryptoFile inTest = new OpenCryptoFile(closeListener, chunkCache, cryptor, headerHolder, chunkIO, CURRENT_FILE_PATH, fileSize, lastModified, openCryptoFileComponent);
+ OpenCryptoFile inTest = new OpenCryptoFile(closeListener, cryptor, headerHolder, chunkIO, CURRENT_FILE_PATH, fileSize, lastModified, openCryptoFileComponent);
@Test
@DisplayName("Skip file header init, if the file header already exists in memory")
@@ -191,14 +190,14 @@ public class FileChannelFactoryTest {
private final AtomicLong realFileSize = new AtomicLong(-1L);
private OpenCryptoFile openCryptoFile;
private CleartextFileChannel cleartextFileChannel;
- private AtomicReference listener;
+ private AtomicReference> listener;
private AtomicReference ciphertextChannel;
@BeforeAll
public void setup() throws IOException {
FS = Jimfs.newFileSystem("OpenCryptoFileTest.FileChannelFactoryTest", Configuration.unix().toBuilder().setAttributeViews("basic", "posix").build());
CURRENT_FILE_PATH = new AtomicReference<>(FS.getPath("currentFile"));
- openCryptoFile = new OpenCryptoFile(closeListener, chunkCache, cryptor, headerHolder, chunkIO, CURRENT_FILE_PATH, realFileSize, lastModified, openCryptoFileComponent);
+ openCryptoFile = new OpenCryptoFile(closeListener,cryptor, headerHolder, chunkIO, CURRENT_FILE_PATH, realFileSize, lastModified, openCryptoFileComponent);
cleartextFileChannel = mock(CleartextFileChannel.class);
listener = new AtomicReference<>();
ciphertextChannel = new AtomicReference<>();
@@ -260,19 +259,6 @@ public void testGetSizeAfterCreatingSecondFileChannel() {
Assertions.assertEquals(0l, openCryptoFile.size().get());
}
-
- @Test
- @Order(20)
- @DisplayName("TRUNCATE_EXISTING leads to chunk cache invalidation")
- public void testTruncateExistingInvalidatesChunkCache() throws IOException {
- Mockito.when(cryptor.fileHeaderCryptor()).thenReturn(fileHeaderCryptor);
- Mockito.when(fileHeaderCryptor.headerSize()).thenReturn(43);
- Files.write(CURRENT_FILE_PATH.get(), new byte[0]);
- EffectiveOpenOptions options = EffectiveOpenOptions.from(EnumSet.of(StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE), readonlyFlag);
- openCryptoFile.newFileChannel(options);
- verify(chunkCache).invalidateStale();
- }
-
@Test
@Order(100)
@DisplayName("closeListener triggers chunkIO.unregisterChannel()")
@@ -280,7 +266,7 @@ public void triggerCloseListener() throws IOException {
Assumptions.assumeTrue(listener.get() != null);
Assumptions.assumeTrue(ciphertextChannel.get() != null);
- listener.get().closed(cleartextFileChannel);
+ listener.get().accept(ciphertextChannel.get());
verify(chunkIO).unregisterChannel(ciphertextChannel.get());
}
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 10e9ba35..240fe666 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 cb97d0b7..6d173b49 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 9f9c93b2..934e1f58 100644
--- a/src/test/java/org/cryptomator/cryptofs/health/dirid/OrphanDirTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/health/dirid/OrphanContentDirTest.java
@@ -5,34 +5,34 @@
import org.cryptomator.cryptofs.DirectoryIdBackup;
import org.cryptomator.cryptofs.VaultConfig;
import org.cryptomator.cryptofs.common.Constants;
+import org.cryptomator.cryptofs.util.TestCryptoException;
import org.cryptomator.cryptolib.api.AuthenticationFailedException;
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,34 @@ class RetrieveDirIdTests {
private OrphanContentDir resultSpy;
+ static List expectedExceptions = List.of(new IOException(), new IllegalStateException(), new TestCryptoException());
+
@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 +271,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 +287,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 +298,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 +426,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 +465,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 +488,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 +508,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 +593,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);
}
-
}
diff --git a/src/test/java/org/cryptomator/cryptofs/migration/MigratorsTest.java b/src/test/java/org/cryptomator/cryptofs/migration/MigratorsTest.java
index 5cb2295e..4bb03835 100644
--- a/src/test/java/org/cryptomator/cryptofs/migration/MigratorsTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/migration/MigratorsTest.java
@@ -7,7 +7,6 @@
import org.cryptomator.cryptofs.VaultConfig;
import org.cryptomator.cryptofs.common.Constants;
-import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
import org.cryptomator.cryptofs.migration.api.MigrationContinuationListener;
import org.cryptomator.cryptofs.migration.api.MigrationProgressListener;
import org.cryptomator.cryptofs.migration.api.Migrator;
@@ -30,12 +29,8 @@
import java.io.IOException;
import java.nio.charset.StandardCharsets;
-import java.nio.file.FileSystem;
import java.nio.file.Files;
-import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.nio.file.spi.FileSystemProvider;
import java.util.Collections;
import java.util.Map;
@@ -44,20 +39,18 @@ public class MigratorsTest {
private Path pathToVault;
private Path vaultConfigPath;
private Path masterkeyPath;
- private FileSystemCapabilityChecker fsCapabilityChecker;
@BeforeEach
public void setup(@TempDir Path tmpDir) {
pathToVault = tmpDir;
vaultConfigPath = tmpDir.resolve("vault.cryptomator");
masterkeyPath = tmpDir.resolve("masterkey.cryptomator");
- fsCapabilityChecker = Mockito.mock(FileSystemCapabilityChecker.class);
}
@Test
@DisplayName("can't determine vault version without masterkey.cryptomator or vault.cryptomator")
public void throwsExceptionIfNeitherMasterkeyNorVaultConfigExists() {
- Migrators migrators = new Migrators(Collections.emptyMap(), fsCapabilityChecker);
+ Migrators migrators = new Migrators(Collections.emptyMap());
IOException thrown = Assertions.assertThrows(IOException.class, () -> {
migrators.needsMigration(pathToVault, "vault.cryptomator", "masterkey.cryptomator");
@@ -92,7 +85,7 @@ public void tearDown() {
@DisplayName("needs migration if vault version < Constants.VAULT_VERSION")
public void testNeedsMigration() throws IOException {
Mockito.when(unverifiedVaultConfig.allegedVaultVersion()).thenReturn(Constants.VAULT_VERSION - 1);
- Migrators migrators = new Migrators(Collections.emptyMap(), fsCapabilityChecker);
+ Migrators migrators = new Migrators(Collections.emptyMap());
boolean result = migrators.needsMigration(pathToVault, "vault.cryptomator", "masterkey.cryptomator");
@@ -103,7 +96,7 @@ public void testNeedsMigration() throws IOException {
@DisplayName("needs no migration if vault version >= Constants.VAULT_VERSION")
public void testNeedsNoMigration() throws IOException {
Mockito.when(unverifiedVaultConfig.allegedVaultVersion()).thenReturn(Constants.VAULT_VERSION);
- Migrators migrators = new Migrators(Collections.emptyMap(), fsCapabilityChecker);
+ Migrators migrators = new Migrators(Collections.emptyMap());
boolean result = migrators.needsMigration(pathToVault, "vault.cryptomator", "masterkey.cryptomator");
@@ -115,7 +108,7 @@ public void testNeedsNoMigration() throws IOException {
public void testMigrateWithoutMigrators() {
Mockito.when(unverifiedVaultConfig.allegedVaultVersion()).thenReturn(42);
- Migrators migrators = new Migrators(Collections.emptyMap(), fsCapabilityChecker);
+ Migrators migrators = new Migrators(Collections.emptyMap());
Assertions.assertThrows(NoApplicableMigratorException.class, () -> {
migrators.migrate(pathToVault, "vault.cryptomator", "masterkey.cryptomator", "secret", MigrationProgressListener.IGNORE, MigrationContinuationListener.CANCEL_ALWAYS);
});
@@ -129,7 +122,7 @@ public void testMigrate() throws NoApplicableMigratorException, CryptoException,
MigrationContinuationListener continuationListener = Mockito.mock(MigrationContinuationListener.class);
Migrator migrator = Mockito.mock(Migrator.class);
Mockito.when(unverifiedVaultConfig.allegedVaultVersion()).thenReturn(0);
- Migrators migrators = new Migrators(Map.of(Migration.ZERO_TO_ONE, migrator), fsCapabilityChecker);
+ Migrators migrators = new Migrators(Map.of(Migration.ZERO_TO_ONE, migrator));
migrators.migrate(pathToVault, "vault.cryptomator", "masterkey.cryptomator", "secret", progressListener, continuationListener);
@@ -141,7 +134,7 @@ public void testMigrate() throws NoApplicableMigratorException, CryptoException,
@SuppressWarnings("deprecation")
public void testMigrateUnsupportedVaultFormat() throws NoApplicableMigratorException, CryptoException, IOException {
Migrator migrator = Mockito.mock(Migrator.class);
- Migrators migrators = new Migrators(Map.of(Migration.ZERO_TO_ONE, migrator), fsCapabilityChecker);
+ Migrators migrators = new Migrators(Map.of(Migration.ZERO_TO_ONE, migrator));
Mockito.when(unverifiedVaultConfig.allegedVaultVersion()).thenReturn(0);
Mockito.doThrow(new UnsupportedVaultFormatException(Integer.MAX_VALUE, 1)).when(migrator).migrate(Mockito.any(), Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any());
@@ -174,7 +167,7 @@ public void tearDown() {
@DisplayName("needs migration if vault version < Constants.VAULT_VERSION")
public void testNeedsMigration() throws IOException {
masterkeyFileAccessClass.when(() -> MasterkeyFileAccess.readAllegedVaultVersion(Mockito.any())).thenReturn(Constants.VAULT_VERSION - 1);
- Migrators migrators = new Migrators(Collections.emptyMap(), fsCapabilityChecker);
+ Migrators migrators = new Migrators(Collections.emptyMap());
boolean result = migrators.needsMigration(pathToVault, "vault.cryptomator", "masterkey.cryptomator");
@@ -185,7 +178,7 @@ public void testNeedsMigration() throws IOException {
@DisplayName("needs no migration if vault version >= Constants.VAULT_VERSION")
public void testNeedsNoMigration() throws IOException {
masterkeyFileAccessClass.when(() -> MasterkeyFileAccess.readAllegedVaultVersion(Mockito.any())).thenReturn(Constants.VAULT_VERSION);
- Migrators migrators = new Migrators(Collections.emptyMap(), fsCapabilityChecker);
+ Migrators migrators = new Migrators(Collections.emptyMap());
boolean result = migrators.needsMigration(pathToVault, "vault.cryptomator", "masterkey.cryptomator");
@@ -197,7 +190,7 @@ public void testNeedsNoMigration() throws IOException {
public void testMigrateWithoutMigrators() {
masterkeyFileAccessClass.when(() -> MasterkeyFileAccess.readAllegedVaultVersion(Mockito.any())).thenReturn(1337);
- Migrators migrators = new Migrators(Collections.emptyMap(), fsCapabilityChecker);
+ Migrators migrators = new Migrators(Collections.emptyMap());
Assertions.assertThrows(NoApplicableMigratorException.class, () -> {
migrators.migrate(pathToVault, "vault.cryptomator", "masterkey.cryptomator", "secret", MigrationProgressListener.IGNORE, MigrationContinuationListener.CANCEL_ALWAYS);
});
@@ -211,7 +204,7 @@ public void testMigrate() throws NoApplicableMigratorException, CryptoException,
MigrationContinuationListener continuationListener = Mockito.mock(MigrationContinuationListener.class);
Migrator migrator = Mockito.mock(Migrator.class);
masterkeyFileAccessClass.when(() -> MasterkeyFileAccess.readAllegedVaultVersion(Mockito.any())).thenReturn(0);
- Migrators migrators = new Migrators(Map.of(Migration.ZERO_TO_ONE, migrator), fsCapabilityChecker);
+ Migrators migrators = new Migrators(Map.of(Migration.ZERO_TO_ONE, migrator));
migrators.migrate(pathToVault, "vault.cryptomator", "masterkey.cryptomator", "secret", progressListener, continuationListener);
@@ -223,7 +216,7 @@ public void testMigrate() throws NoApplicableMigratorException, CryptoException,
@SuppressWarnings("deprecation")
public void testMigrateUnsupportedVaultFormat() throws NoApplicableMigratorException, CryptoException, IOException {
Migrator migrator = Mockito.mock(Migrator.class);
- Migrators migrators = new Migrators(Map.of(Migration.ZERO_TO_ONE, migrator), fsCapabilityChecker);
+ Migrators migrators = new Migrators(Map.of(Migration.ZERO_TO_ONE, migrator));
masterkeyFileAccessClass.when(() -> MasterkeyFileAccess.readAllegedVaultVersion(Mockito.any())).thenReturn(0);
Mockito.doThrow(new UnsupportedVaultFormatException(Integer.MAX_VALUE, 1)).when(migrator).migrate(Mockito.any(), Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any());
diff --git a/src/test/java/org/cryptomator/cryptofs/util/TestCryptoException.java b/src/test/java/org/cryptomator/cryptofs/util/TestCryptoException.java
new file mode 100644
index 00000000..7197ef6e
--- /dev/null
+++ b/src/test/java/org/cryptomator/cryptofs/util/TestCryptoException.java
@@ -0,0 +1,11 @@
+package org.cryptomator.cryptofs.util;
+
+import org.cryptomator.cryptolib.api.CryptoException;
+
+public class TestCryptoException extends CryptoException {
+
+ public TestCryptoException() {
+ super();
+ }
+
+}