process(Node node) {
+ if (node.fullCiphertextFileName.endsWith(Constants.CRYPTOMATOR_FILE_SUFFIX)) {
+ return c9rProcessor.process(node).flatMap(brokenDirFilter::process);
+ } else if (node.fullCiphertextFileName.endsWith(Constants.DEFLATED_FILE_SUFFIX)) {
+ return c9sProcessor.process(node).flatMap(brokenDirFilter::process);
+ } else {
+ return Stream.empty();
+ }
+ }
+
+}
diff --git a/src/main/java/org/cryptomator/cryptofs/dir/package-info.java b/src/main/java/org/cryptomator/cryptofs/dir/package-info.java
new file mode 100644
index 00000000..e4f64f92
--- /dev/null
+++ b/src/main/java/org/cryptomator/cryptofs/dir/package-info.java
@@ -0,0 +1,12 @@
+/**
+ * This package contains classes used during directory listing.
+ *
+ * When calling {@link java.nio.file.Files#newDirectoryStream(java.nio.file.Path) Files.newDirectoryStream(cleartextPath)},
+ * {@link org.cryptomator.cryptofs.dir.DirectoryStreamFactory} will determine the corresponding ciphertextPath
+ * and open a DirectoryStream on it.
+ *
+ * Each node will then be passed through a pipes-and-filters system consisting of the vairous classes in this package, resulting in cleartext nodes.
+ *
+ * As a side effect certain auto-repair steps are applied, if non-standard ciphertext files are encountered and deemed recoverable.
+ */
+package org.cryptomator.cryptofs.dir;
\ No newline at end of file
diff --git a/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFile.java b/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFile.java
index 1217d628..d9352fa6 100644
--- a/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFile.java
+++ b/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFile.java
@@ -8,7 +8,6 @@
*******************************************************************************/
package org.cryptomator.cryptofs.fh;
-import com.google.common.base.Preconditions;
import org.cryptomator.cryptofs.EffectiveOpenOptions;
import org.cryptomator.cryptofs.ch.ChannelComponent;
import org.cryptomator.cryptofs.ch.CleartextFileChannel;
diff --git a/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFiles.java b/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFiles.java
index 02018756..dea5f027 100644
--- a/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFiles.java
+++ b/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFiles.java
@@ -32,12 +32,12 @@
@CryptoFileSystemScoped
public class OpenCryptoFiles implements Closeable {
- private final CryptoFileSystemComponent component;
+ private final OpenCryptoFileComponent.Builder openCryptoFileComponentBuilder;
private final ConcurrentMap openCryptoFiles = new ConcurrentHashMap<>();
@Inject
- OpenCryptoFiles(CryptoFileSystemComponent component) {
- this.component = component;
+ OpenCryptoFiles(OpenCryptoFileComponent.Builder openCryptoFileComponentBuilder) {
+ this.openCryptoFileComponentBuilder = openCryptoFileComponentBuilder;
}
/**
@@ -67,7 +67,7 @@ public OpenCryptoFile getOrCreate(Path ciphertextPath) {
}
private OpenCryptoFile create(Path normalizedPath) {
- OpenCryptoFileComponent openCryptoFileComponent = component.newOpenCryptoFileComponent()
+ OpenCryptoFileComponent openCryptoFileComponent = openCryptoFileComponentBuilder
.path(normalizedPath)
.onClose(openCryptoFiles::remove)
.build();
diff --git a/src/main/java/org/cryptomator/cryptofs/migration/Migration.java b/src/main/java/org/cryptomator/cryptofs/migration/Migration.java
index 932506c8..c214ca2d 100644
--- a/src/main/java/org/cryptomator/cryptofs/migration/Migration.java
+++ b/src/main/java/org/cryptomator/cryptofs/migration/Migration.java
@@ -14,11 +14,16 @@ enum Migration {
/**
* Migrates vault format 5 to 6.
*/
- FIVE_TO_SIX(5);
+ FIVE_TO_SIX(5),
+
+ /**
+ * Migrates vault format 5 to 6.
+ */
+ SIX_TO_SEVEN(6);
private final int applicableVersion;
- private Migration(int applicableVersion) {
+ Migration(int applicableVersion) {
this.applicableVersion = applicableVersion;
}
diff --git a/src/main/java/org/cryptomator/cryptofs/migration/MigrationModule.java b/src/main/java/org/cryptomator/cryptofs/migration/MigrationModule.java
index d5a99ad8..d69c2efc 100644
--- a/src/main/java/org/cryptomator/cryptofs/migration/MigrationModule.java
+++ b/src/main/java/org/cryptomator/cryptofs/migration/MigrationModule.java
@@ -15,6 +15,7 @@
import dagger.multibindings.IntoMap;
import org.cryptomator.cryptofs.migration.api.Migrator;
import org.cryptomator.cryptofs.migration.v6.Version6Migrator;
+import org.cryptomator.cryptofs.migration.v7.Version7Migrator;
import org.cryptomator.cryptolib.api.CryptorProvider;
import static java.lang.annotation.ElementType.METHOD;
@@ -41,19 +42,12 @@ Migrator provideVersion6Migrator(Version6Migrator migrator) {
return migrator;
}
- // @Provides
- // @IntoMap
- // @MigratorKey(Migration.SIX_TO_SEVEN)
- // Migrator provideVersion7Migrator(Version7Migrator migrator) {
- // return migrator;
- // }
- //
- // @Provides
- // @IntoMap
- // @MigratorKey(Migration.FIVE_TO_SEVEN)
- // Migrator provideVersion7Migrator(Version6Migrator v6Migrator, Version7Migrator v7Migrator) {
- // return v6Migrator.andThen(v7Migrator);
- // }
+ @Provides
+ @IntoMap
+ @MigratorKey(Migration.SIX_TO_SEVEN)
+ Migrator provideVersion7Migrator(Version7Migrator migrator) {
+ return migrator;
+ }
@Documented
@Target(METHOD)
diff --git a/src/main/java/org/cryptomator/cryptofs/migration/Migrators.java b/src/main/java/org/cryptomator/cryptofs/migration/Migrators.java
index f42d17dc..e8506250 100644
--- a/src/main/java/org/cryptomator/cryptofs/migration/Migrators.java
+++ b/src/main/java/org/cryptomator/cryptofs/migration/Migrators.java
@@ -15,7 +15,8 @@
import javax.inject.Inject;
-import org.cryptomator.cryptofs.Constants;
+import org.cryptomator.cryptofs.common.Constants;
+import org.cryptomator.cryptofs.migration.api.MigrationProgressListener;
import org.cryptomator.cryptofs.migration.api.Migrator;
import org.cryptomator.cryptofs.migration.api.NoApplicableMigratorException;
import org.cryptomator.cryptolib.Cryptors;
@@ -31,7 +32,7 @@
*
*
* if (Migrators.get().{@link #needsMigration(Path, String) needsMigration(pathToVault, masterkeyFileName)}) {
- * Migrators.get().{@link #migrate(Path, String, CharSequence) migrate(pathToVault, masterkeyFileName, passphrase)};
+ * Migrators.get().{@link #migrate(Path, String, CharSequence, MigrationProgressListener) migrate(pathToVault, masterkeyFileName, passphrase, migrationProgressListener)};
* }
*
*
@@ -88,14 +89,14 @@ public boolean needsMigration(Path pathToVault, String masterkeyFilename) throws
* @throws InvalidPassphraseException If the passphrase could not be used to unlock the vault
* @throws IOException if an I/O error occurs migrating the vault
*/
- public void migrate(Path pathToVault, String masterkeyFilename, CharSequence passphrase) throws NoApplicableMigratorException, InvalidPassphraseException, IOException {
+ public void migrate(Path pathToVault, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener) throws NoApplicableMigratorException, InvalidPassphraseException, IOException {
Path masterKeyPath = pathToVault.resolve(masterkeyFilename);
byte[] keyFileContents = Files.readAllBytes(masterKeyPath);
KeyFile keyFile = KeyFile.parse(keyFileContents);
try {
Migrator migrator = findApplicableMigrator(keyFile.getVersion()).orElseThrow(NoApplicableMigratorException::new);
- migrator.migrate(pathToVault, masterkeyFilename, passphrase);
+ migrator.migrate(pathToVault, masterkeyFilename, passphrase, progressListener);
} catch (UnsupportedVaultFormatException e) {
// might be a tampered masterkey file, as this exception is also thrown if the vault version MAC is not authentic.
throw new IllegalStateException("Vault version checked beforehand but not supported by migrator.");
@@ -103,7 +104,6 @@ public void migrate(Path pathToVault, String masterkeyFilename, CharSequence pas
}
private Optional findApplicableMigrator(int version) {
- // TODO return "5->6->7" instead of "5->6" and "6->7", if possible
return migrators.entrySet().stream().filter(entry -> entry.getKey().isApplicable(version)).map(Map.Entry::getValue).findAny();
}
diff --git a/src/main/java/org/cryptomator/cryptofs/migration/api/MigrationProgressListener.java b/src/main/java/org/cryptomator/cryptofs/migration/api/MigrationProgressListener.java
new file mode 100644
index 00000000..9fe9ef68
--- /dev/null
+++ b/src/main/java/org/cryptomator/cryptofs/migration/api/MigrationProgressListener.java
@@ -0,0 +1,33 @@
+package org.cryptomator.cryptofs.migration.api;
+
+@FunctionalInterface
+public interface MigrationProgressListener {
+
+ /**
+ * Called on every step during migration that might change the progress.
+ *
+ * @param state Current state of the migration
+ * @param progress Progress that should be between 0.0 and 1.0 but due to inaccurate estimations it might even be 1.1
+ */
+ void update(ProgressState state, double progress);
+
+ enum ProgressState {
+ /**
+ * Migration recently started. The progress can't be calculated yet.
+ */
+ INITIALIZING,
+
+ /**
+ * Migration is running and progress can be calculated.
+ *
+ * Any long-running tasks should (if possible) happen in this state.
+ */
+ MIGRATING,
+
+ /**
+ * Cleanup after success or failure is running. Remaining time is in unknown.
+ */
+ FINALIZING
+ }
+
+}
diff --git a/src/main/java/org/cryptomator/cryptofs/migration/api/Migrator.java b/src/main/java/org/cryptomator/cryptofs/migration/api/Migrator.java
index 0319e00f..3d6790f6 100644
--- a/src/main/java/org/cryptomator/cryptofs/migration/api/Migrator.java
+++ b/src/main/java/org/cryptomator/cryptofs/migration/api/Migrator.java
@@ -26,19 +26,21 @@ public interface Migrator {
* @throws UnsupportedVaultFormatException
* @throws IOException
*/
- void migrate(Path vaultRoot, String masterkeyFilename, CharSequence passphrase) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException;
+ default void migrate(Path vaultRoot, String masterkeyFilename, CharSequence passphrase) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException {
+ migrate(vaultRoot, masterkeyFilename, passphrase, (state, progress) -> {});
+ }
/**
- * Chains this migrator with a consecutive migrator.
- *
- * @param nextMigration The next migrator able to read the vault format created by this migrator.
- * @return A combined migrator performing both steps in order.
+ * Performs the migration this migrator is built for.
+ *
+ * @param vaultRoot
+ * @param masterkeyFilename
+ * @param passphrase
+ * @param progressListener
+ * @throws InvalidPassphraseException
+ * @throws UnsupportedVaultFormatException
+ * @throws IOException
*/
- default Migrator andThen(Migrator nextMigration) {
- return (Path vaultRoot, String masterkeyFilename, CharSequence passphrase) -> {
- migrate(vaultRoot, masterkeyFilename, passphrase);
- nextMigration.migrate(vaultRoot, masterkeyFilename, passphrase);
- };
- }
+ void migrate(Path vaultRoot, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException;
}
diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v6/Version6Migrator.java b/src/main/java/org/cryptomator/cryptofs/migration/v6/Version6Migrator.java
index 82121960..1505d6aa 100644
--- a/src/main/java/org/cryptomator/cryptofs/migration/v6/Version6Migrator.java
+++ b/src/main/java/org/cryptomator/cryptofs/migration/v6/Version6Migrator.java
@@ -15,8 +15,9 @@
import javax.inject.Inject;
-import org.cryptomator.cryptofs.BackupUtil;
-import org.cryptomator.cryptofs.Constants;
+import org.cryptomator.cryptofs.common.MasterkeyBackupFileHasher;
+import org.cryptomator.cryptofs.common.Constants;
+import org.cryptomator.cryptofs.migration.api.MigrationProgressListener;
import org.cryptomator.cryptofs.migration.api.Migrator;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.cryptolib.api.CryptorProvider;
@@ -38,16 +39,20 @@ public Version6Migrator(CryptorProvider cryptorProvider) {
}
@Override
- public void migrate(Path vaultRoot, String masterkeyFilename, CharSequence passphrase) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException {
+ public void migrate(Path vaultRoot, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException {
LOG.info("Upgrading {} from version 5 to version 6.", vaultRoot);
+ progressListener.update(MigrationProgressListener.ProgressState.INITIALIZING, 0.0);
Path masterkeyFile = vaultRoot.resolve(masterkeyFilename);
byte[] fileContentsBeforeUpgrade = Files.readAllBytes(masterkeyFile);
KeyFile keyFile = KeyFile.parse(fileContentsBeforeUpgrade);
try (Cryptor cryptor = cryptorProvider.createFromKeyFile(keyFile, passphrase, 5)) {
// create backup, as soon as we know the password was correct:
- Path masterkeyBackupFile = vaultRoot.resolve(masterkeyFilename + BackupUtil.generateFileIdSuffix(fileContentsBeforeUpgrade) + Constants.MASTERKEY_BACKUP_SUFFIX);
+ Path masterkeyBackupFile = vaultRoot.resolve(masterkeyFilename + MasterkeyBackupFileHasher.generateFileIdSuffix(fileContentsBeforeUpgrade) + Constants.MASTERKEY_BACKUP_SUFFIX);
Files.copy(masterkeyFile, masterkeyBackupFile, StandardCopyOption.REPLACE_EXISTING);
LOG.info("Backed up masterkey from {} to {}.", masterkeyFile.getFileName(), masterkeyBackupFile.getFileName());
+
+ progressListener.update(MigrationProgressListener.ProgressState.FINALIZING, 0.0);
+
// rewrite masterkey file with normalized passphrase:
byte[] fileContentsAfterUpgrade = cryptor.writeKeysToMasterkeyFile(Normalizer.normalize(passphrase, Form.NFC), 6).serialize();
Files.write(masterkeyFile, fileContentsAfterUpgrade, StandardOpenOption.TRUNCATE_EXISTING);
diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v7/FilePathMigration.java b/src/main/java/org/cryptomator/cryptofs/migration/v7/FilePathMigration.java
new file mode 100644
index 00000000..4a08097d
--- /dev/null
+++ b/src/main/java/org/cryptomator/cryptofs/migration/v7/FilePathMigration.java
@@ -0,0 +1,244 @@
+package org.cryptomator.cryptofs.migration.v7;
+
+import com.google.common.base.Throwables;
+import com.google.common.io.BaseEncoding;
+import org.cryptomator.cryptolib.common.MessageDigestSupplier;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/**
+ * Helper class responsible of the migration of a single file
+ *
+ * Filename migration is a two-step process: Disassembly of the old path and assembly of a new path.
+ */
+class FilePathMigration {
+
+ private static final String OLD_SHORTENED_FILENAME_SUFFIX = ".lng";
+ private static final Pattern OLD_SHORTENED_FILENAME_PATTERN = Pattern.compile("[A-Z2-7]{32}");
+ private static final Pattern OLD_CANONICAL_FILENAME_PATTERN = Pattern.compile("(0|1S)?([A-Z2-7]{8})*[A-Z2-7=]{8}");
+ private static final BaseEncoding BASE32 = BaseEncoding.base32();
+ private static final BaseEncoding BASE64 = BaseEncoding.base64Url();
+ private static final int SHORTENING_THRESHOLD = 222; // see calculations in https://github.com/cryptomator/cryptofs/issues/60
+ private static final String OLD_DIRECTORY_PREFIX = "0";
+ private static final String OLD_SYMLINK_PREFIX = "1S";
+ private static final String NEW_REGULAR_SUFFIX = ".c9r";
+ private static final String NEW_SHORTENED_SUFFIX = ".c9s";
+ private static final int MAX_FILENAME_BUFFER_SIZE = 10 * 1024;
+ private static final String NEW_SHORTENED_METADATA_FILE = "name.c9s";
+ private static final String NEW_DIR_FILE = "dir.c9r";
+ private static final String NEW_CONTENTS_FILE = "contents.c9r";
+ private static final String NEW_SYMLINK_FILE = "symlink.c9r";
+
+ private final Path oldPath;
+ private final String oldCanonicalName;
+
+ /**
+ * @param oldPath The actual file path before migration
+ * @param oldCanonicalName The inflated old filename without any conflicting pre- or suffixes but including the file type prefix
+ */
+ FilePathMigration(Path oldPath, String oldCanonicalName) {
+ assert OLD_CANONICAL_FILENAME_PATTERN.matcher(oldCanonicalName).matches();
+ this.oldPath = oldPath;
+ this.oldCanonicalName = oldCanonicalName;
+ }
+
+ /**
+ * Starts a migration of the given file.
+ *
+ * @param vaultRoot Path to the vault's base directory (parent of d/
and m/
).
+ * @param oldPath Path of an existing file inside the d/
directory of a vault. May be a normal file, directory file or symlink as well as conflicting copies.
+ * @return A new instance of FileNameMigration
+ * @throws IOException Non-recoverable I/O error, such as {@link UninflatableFileException}s
+ */
+ public static Optional parse(Path vaultRoot, Path oldPath) throws IOException {
+ final String oldFileName = oldPath.getFileName().toString();
+ final String canonicalOldFileName;
+ if (oldFileName.endsWith(NEW_REGULAR_SUFFIX) || oldFileName.endsWith(NEW_SHORTENED_SUFFIX)) {
+ // make sure to not match already migrated files
+ // (since BASE32 is a subset of BASE64, pure pattern matching could accidentally match those)
+ return Optional.empty();
+ } else if (oldFileName.endsWith(OLD_SHORTENED_FILENAME_SUFFIX)) {
+ Matcher matcher = OLD_SHORTENED_FILENAME_PATTERN.matcher(oldFileName);
+ if (matcher.find()) {
+ canonicalOldFileName = inflate(vaultRoot, matcher.group() + OLD_SHORTENED_FILENAME_SUFFIX);
+ } else {
+ return Optional.empty();
+ }
+ } else {
+ Matcher matcher = OLD_CANONICAL_FILENAME_PATTERN.matcher(oldFileName);
+ if (matcher.find()) {
+ canonicalOldFileName = matcher.group();
+ } else {
+ return Optional.empty();
+ }
+ }
+ return Optional.of(new FilePathMigration(oldPath, canonicalOldFileName));
+ }
+
+ /**
+ * Resolves the canonical name of a deflated file represented by the given longFileName
.
+ *
+ * @param vaultRoot Path to the vault's base directory (parent of d/
and m/
).
+ * @param longFileName Canonical name of the {@value #OLD_SHORTENED_FILENAME_SUFFIX} file.
+ * @return The inflated filename
+ * @throws UninflatableFileException If the file could not be inflated due to missing or malformed metadata.
+ */
+ // visible for testing
+ static String inflate(Path vaultRoot, String longFileName) throws UninflatableFileException {
+ Path metadataFilePath = vaultRoot.resolve("m/" + longFileName.substring(0, 2) + "/" + longFileName.substring(2, 4) + "/" + longFileName);
+ try (SeekableByteChannel ch = Files.newByteChannel(metadataFilePath, StandardOpenOption.READ)) {
+ if (ch.size() > MAX_FILENAME_BUFFER_SIZE) {
+ throw new UninflatableFileException("Unexpectedly large file: " + metadataFilePath);
+ }
+ ByteBuffer buf = ByteBuffer.allocate((int) Math.min(ch.size(), MAX_FILENAME_BUFFER_SIZE));
+ ch.read(buf);
+ buf.flip();
+ return UTF_8.decode(buf).toString();
+ } catch (IOException e) {
+ Throwables.throwIfInstanceOf(e, UninflatableFileException.class);
+ throw new UninflatableFileException("Failed to read metadata file " + metadataFilePath, e);
+ }
+ }
+
+ /**
+ * Migrates the path. This method attempts to give a migrated file its canonical name.
+ * In case of conflicts with existing files a suffix will be added, which will later trigger the conflict resolver.
+ *
+ * @return The path after migrating
+ * @throws IOException Non-recoverable I/O error
+ */
+ public Path migrate() throws IOException {
+ final String canonicalInflatedName = getNewInflatedName();
+ final String canonicalDeflatedName = getNewDeflatedName();
+ final boolean isShortened = !canonicalInflatedName.equals(canonicalDeflatedName);
+
+ FileAlreadyExistsException attemptsExceeded = new FileAlreadyExistsException(oldPath.toString(), oldPath.resolveSibling(canonicalDeflatedName).toString(), "");
+ String attemptSuffix = "";
+
+ for (int i = 1; i <= 3; i++) {
+ try {
+ Path newPath = getTargetPath(attemptSuffix);
+ if (isShortened || isDirectory() || isSymlink()) {
+ Files.createDirectory(newPath.getParent());
+ }
+ if (isShortened) {
+ Path metadataFilePath = newPath.resolveSibling(NEW_SHORTENED_METADATA_FILE);
+ Files.write(metadataFilePath, canonicalInflatedName.getBytes(UTF_8));
+ }
+ return Files.move(oldPath, newPath);
+ } catch (FileAlreadyExistsException e) {
+ attemptSuffix = "_" + i;
+ attemptsExceeded.addSuppressed(e);
+ continue;
+ }
+ }
+ throw attemptsExceeded;
+ }
+
+ /**
+ * @param attemptSuffix Empty string or anything starting with a non base64 delimiter
+ * @return The path after successful migration of {@link #oldPath} if migration is successful for the given attemptSuffix
+ */
+ // visible for testing
+ Path getTargetPath(String attemptSuffix) {
+ final String canonicalInflatedName = getNewInflatedName();
+ final String canonicalDeflatedName = getNewDeflatedName();
+ final boolean isShortened = !canonicalInflatedName.equals(canonicalDeflatedName);
+
+ final String inflatedName = canonicalInflatedName.substring(0, canonicalInflatedName.length() - NEW_REGULAR_SUFFIX.length()) + attemptSuffix + NEW_REGULAR_SUFFIX;
+ final String deflatedName = canonicalDeflatedName.substring(0, canonicalDeflatedName.length() - NEW_SHORTENED_SUFFIX.length()) + attemptSuffix + NEW_SHORTENED_SUFFIX;
+
+ if (isShortened) {
+ if (isDirectory()) {
+ return oldPath.resolveSibling(deflatedName).resolve(NEW_DIR_FILE);
+ } else if (isSymlink()) {
+ return oldPath.resolveSibling(deflatedName).resolve(NEW_SYMLINK_FILE);
+ } else {
+ return oldPath.resolveSibling(deflatedName).resolve(NEW_CONTENTS_FILE);
+ }
+ } else {
+ if (isDirectory()) {
+ return oldPath.resolveSibling(inflatedName).resolve(NEW_DIR_FILE);
+ } else if (isSymlink()) {
+ return oldPath.resolveSibling(inflatedName).resolve(NEW_SYMLINK_FILE);
+ } else {
+ return oldPath.resolveSibling(inflatedName);
+ }
+ }
+ }
+
+ public Path getOldPath() {
+ return oldPath;
+ }
+
+ // visible for testing
+ String getOldCanonicalName() {
+ return oldCanonicalName;
+ }
+
+ /**
+ * @return {@link #oldCanonicalName} without any preceeding "0" or "1S" in case of dirs or symlinks.
+ */
+ // visible for testing
+ String getOldCanonicalNameWithoutTypePrefix() {
+ if (oldCanonicalName.startsWith(OLD_DIRECTORY_PREFIX)) {
+ return oldCanonicalName.substring(OLD_DIRECTORY_PREFIX.length());
+ } else if (oldCanonicalName.startsWith(OLD_SYMLINK_PREFIX)) {
+ return oldCanonicalName.substring(OLD_SYMLINK_PREFIX.length());
+ } else {
+ return oldCanonicalName;
+ }
+ }
+
+ /**
+ * @return BASE64-encode(BASE32-decode({@link #getOldCanonicalNameWithoutTypePrefix oldCanonicalNameWithoutPrefix})) + {@value #NEW_REGULAR_SUFFIX}
+ */
+ // visible for testing
+ String getNewInflatedName() {
+ byte[] decoded = BASE32.decode(getOldCanonicalNameWithoutTypePrefix());
+ return BASE64.encode(decoded) + NEW_REGULAR_SUFFIX;
+ }
+
+ /**
+ * @return {@link #getNewInflatedName() newInflatedName} if it is shorter than {@link #SHORTENING_THRESHOLD}, else BASE64(SHA1(newInflatedName)) + ".c9s"
+ */
+ // visible for testing
+ String getNewDeflatedName() {
+ String inflatedName = getNewInflatedName();
+ if (inflatedName.length() > SHORTENING_THRESHOLD) {
+ byte[] longFileNameBytes = inflatedName.getBytes(UTF_8);
+ byte[] hash = MessageDigestSupplier.SHA1.get().digest(longFileNameBytes);
+ return BASE64.encode(hash) + NEW_SHORTENED_SUFFIX;
+ } else {
+ return inflatedName;
+ }
+ }
+
+ /**
+ * @return true
if {@link #oldCanonicalName} starts with "0"
+ */
+ // visible for testing
+ boolean isDirectory() {
+ return oldCanonicalName.startsWith(OLD_DIRECTORY_PREFIX);
+ }
+
+ /**
+ * @return true
if {@link #oldCanonicalName} starts with "1S"
+ */
+ // visible for testing
+ boolean isSymlink() {
+ return oldCanonicalName.startsWith(OLD_SYMLINK_PREFIX);
+ }
+
+}
diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v7/MigratingVisitor.java b/src/main/java/org/cryptomator/cryptofs/migration/v7/MigratingVisitor.java
new file mode 100644
index 00000000..551632c6
--- /dev/null
+++ b/src/main/java/org/cryptomator/cryptofs/migration/v7/MigratingVisitor.java
@@ -0,0 +1,66 @@
+package org.cryptomator.cryptofs.migration.v7;
+
+import org.cryptomator.cryptofs.migration.api.MigrationProgressListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Optional;
+
+class MigratingVisitor extends SimpleFileVisitor {
+
+ private static final Logger LOG = LoggerFactory.getLogger(MigratingVisitor.class);
+
+ private final Path vaultRoot;
+ private final MigrationProgressListener progressListener;
+ private final long estimatedTotalFiles;
+
+ public MigratingVisitor(Path vaultRoot, MigrationProgressListener progressListener, long estimatedTotalFiles) {
+ this.vaultRoot = vaultRoot;
+ this.progressListener = progressListener;
+ this.estimatedTotalFiles = estimatedTotalFiles;
+ }
+
+ private Collection migrationsInCurrentDir = new ArrayList<>();
+ private long migratedFiles = 0;
+
+ // Step 1: Collect files to be migrated
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+ final Optional migration;
+ try {
+ migration = FilePathMigration.parse(vaultRoot, file);
+ } catch (UninflatableFileException e) {
+ LOG.warn("SKIP {} because inflation failed.", file);
+ return FileVisitResult.CONTINUE;
+ }
+ migration.ifPresent(migrationsInCurrentDir::add);
+ return FileVisitResult.CONTINUE;
+ }
+
+ // Step 2: Only after visiting this dir, we will perform any changes to avoid "ConcurrentModificationExceptions"
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+ for (FilePathMigration migration : migrationsInCurrentDir) {
+ migratedFiles++;
+ progressListener.update(MigrationProgressListener.ProgressState.MIGRATING, (double) migratedFiles / estimatedTotalFiles);
+ try {
+ Path migratedFile = migration.migrate();
+ LOG.info("MOVED {} to {}", migration.getOldPath(), migratedFile);
+ } catch (FileAlreadyExistsException e) {
+ LOG.error("Failed to migrate " + migration.getOldPath() + " due to FileAlreadyExistsException. Already migrated on a different machine?.", e);
+ return FileVisitResult.TERMINATE;
+ }
+ }
+ migrationsInCurrentDir.clear();
+ return FileVisitResult.CONTINUE;
+ }
+
+}
diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v7/UninflatableFileException.java b/src/main/java/org/cryptomator/cryptofs/migration/v7/UninflatableFileException.java
new file mode 100644
index 00000000..8d5e4705
--- /dev/null
+++ b/src/main/java/org/cryptomator/cryptofs/migration/v7/UninflatableFileException.java
@@ -0,0 +1,14 @@
+package org.cryptomator.cryptofs.migration.v7;
+
+import java.io.IOException;
+
+public class UninflatableFileException extends IOException {
+
+ public UninflatableFileException(String message) {
+ super(message);
+ }
+
+ public UninflatableFileException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java b/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java
new file mode 100644
index 00000000..01fd9c92
--- /dev/null
+++ b/src/main/java/org/cryptomator/cryptofs/migration/v7/Version7Migrator.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the accompanying LICENSE file.
+ *******************************************************************************/
+package org.cryptomator.cryptofs.migration.v7;
+
+import org.cryptomator.cryptofs.common.MasterkeyBackupFileHasher;
+import org.cryptomator.cryptofs.common.Constants;
+import org.cryptomator.cryptofs.common.DeletingFileVisitor;
+import org.cryptomator.cryptofs.migration.api.MigrationProgressListener;
+import org.cryptomator.cryptofs.migration.api.Migrator;
+import org.cryptomator.cryptolib.api.Cryptor;
+import org.cryptomator.cryptolib.api.CryptorProvider;
+import org.cryptomator.cryptolib.api.InvalidPassphraseException;
+import org.cryptomator.cryptolib.api.KeyFile;
+import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.nio.file.FileVisitOption;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.EnumSet;
+import java.util.concurrent.atomic.LongAdder;
+
+public class Version7Migrator implements Migrator {
+
+ private static final Logger LOG = LoggerFactory.getLogger(Version7Migrator.class);
+
+ private final CryptorProvider cryptorProvider;
+
+ @Inject
+ public Version7Migrator(CryptorProvider cryptorProvider) {
+ this.cryptorProvider = cryptorProvider;
+ }
+
+ @Override
+ public void migrate(Path vaultRoot, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException {
+ LOG.info("Upgrading {} from version 6 to version 7.", vaultRoot);
+ progressListener.update(MigrationProgressListener.ProgressState.INITIALIZING, 0.0);
+ Path masterkeyFile = vaultRoot.resolve(masterkeyFilename);
+ byte[] fileContentsBeforeUpgrade = Files.readAllBytes(masterkeyFile);
+ KeyFile keyFile = KeyFile.parse(fileContentsBeforeUpgrade);
+ try (Cryptor cryptor = cryptorProvider.createFromKeyFile(keyFile, passphrase, 6)) {
+ // create backup, as soon as we know the password was correct:
+ Path masterkeyBackupFile = vaultRoot.resolve(masterkeyFilename + MasterkeyBackupFileHasher.generateFileIdSuffix(fileContentsBeforeUpgrade) + Constants.MASTERKEY_BACKUP_SUFFIX);
+ Files.copy(masterkeyFile, masterkeyBackupFile, StandardCopyOption.REPLACE_EXISTING);
+ LOG.info("Backed up masterkey from {} to {}.", masterkeyFile.getFileName(), masterkeyBackupFile.getFileName());
+
+ long toBeMigrated = countFileNames(vaultRoot);
+ if (toBeMigrated > 0) {
+ migrateFileNames(vaultRoot, progressListener, toBeMigrated);
+ }
+
+ progressListener.update(MigrationProgressListener.ProgressState.FINALIZING, 0.0);
+
+ // remove deprecated /m/ directory
+ Files.walkFileTree(vaultRoot.resolve("m"), DeletingFileVisitor.INSTANCE);
+
+ // rewrite masterkey file with normalized passphrase:
+ byte[] fileContentsAfterUpgrade = cryptor.writeKeysToMasterkeyFile(passphrase, 7).serialize();
+ Files.write(masterkeyFile, fileContentsAfterUpgrade, StandardOpenOption.TRUNCATE_EXISTING);
+ LOG.info("Updated masterkey.");
+ }
+ LOG.info("Upgraded {} from version 6 to version 7.", vaultRoot);
+ }
+
+ private long countFileNames(Path vaultRoot) throws IOException {
+ LongAdder counter = new LongAdder();
+ Path dataDir = vaultRoot.resolve("d");
+ Files.walkFileTree(dataDir, EnumSet.noneOf(FileVisitOption.class), 3, new SimpleFileVisitor() {
+
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
+ counter.increment();
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ return counter.sum();
+ }
+
+ private void migrateFileNames(Path vaultRoot, MigrationProgressListener progressListener, long totalFiles) throws IOException {
+ assert totalFiles > 0;
+ Path dataDir = vaultRoot.resolve("d");
+ Files.walkFileTree(dataDir, EnumSet.noneOf(FileVisitOption.class), 3, new MigratingVisitor(vaultRoot, progressListener, totalFiles));
+ }
+
+}
diff --git a/src/test/java/org/cryptomator/cryptofs/CiphertextFileTypeTest.java b/src/test/java/org/cryptomator/cryptofs/CiphertextFileTypeTest.java
deleted file mode 100644
index 8588b74c..00000000
--- a/src/test/java/org/cryptomator/cryptofs/CiphertextFileTypeTest.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package org.cryptomator.cryptofs;
-
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.CsvSource;
-
-import java.util.Arrays;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-public class CiphertextFileTypeTest {
-
- @Test
- public void testNonTrivialValues() {
- Set result = CiphertextFileType.nonTrivialValues().collect(Collectors.toSet());
- Assertions.assertFalse(result.contains(CiphertextFileType.FILE));
- Assertions.assertTrue(result.containsAll(Arrays.asList(CiphertextFileType.DIRECTORY, CiphertextFileType.SYMLINK)));
- }
-
- @DisplayName("CiphertextFileType.forFileName(...)")
- @ParameterizedTest(name = "{0}")
- @CsvSource(value = {"FOO, ''", "0FOO, 0", "1SFOO, 1S", "1XFOO, ''"})
- public void testNonTrivialValues(String filename, String expectedPrefix) {
- CiphertextFileType result = CiphertextFileType.forFileName(filename);
- Assertions.assertEquals(expectedPrefix, result.getPrefix());
- }
-
-}
diff --git a/src/test/java/org/cryptomator/cryptofs/ConflictResolverTest.java b/src/test/java/org/cryptomator/cryptofs/ConflictResolverTest.java
deleted file mode 100644
index 5cbb3ab7..00000000
--- a/src/test/java/org/cryptomator/cryptofs/ConflictResolverTest.java
+++ /dev/null
@@ -1,201 +0,0 @@
-package org.cryptomator.cryptofs;
-
-import org.cryptomator.cryptolib.api.AuthenticationFailedException;
-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.Test;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.ValueSource;
-import org.mockito.ArgumentMatcher;
-import org.mockito.Mockito;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import java.io.IOException;
-import java.lang.reflect.Field;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.nio.channels.spi.AbstractInterruptibleChannel;
-import java.nio.file.FileSystem;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.Path;
-import java.nio.file.spi.FileSystemProvider;
-
-public class ConflictResolverTest {
-
- private LongFileNameProvider longFileNameProvider;
- private Cryptor cryptor;
- private FileNameCryptor filenameCryptor;
- private ConflictResolver conflictResolver;
- private String dirId;
- private Path testFile;
- private Path testFileName;
- private Path testDir;
- private FileSystem testFileSystem;
- private FileSystemProvider testFileSystemProvider;
-
- @BeforeEach
- public void setup() {
- this.longFileNameProvider = Mockito.mock(LongFileNameProvider.class);
- this.cryptor = Mockito.mock(Cryptor.class);
- this.filenameCryptor = Mockito.mock(FileNameCryptor.class);
- this.conflictResolver = new ConflictResolver(longFileNameProvider, cryptor);
- this.dirId = "foo";
- this.testFile = Mockito.mock(Path.class);
- this.testFileName = Mockito.mock(Path.class);
- this.testDir = Mockito.mock(Path.class);
- this.testFileSystem = Mockito.mock(FileSystem.class);
- this.testFileSystemProvider = Mockito.mock(FileSystemProvider.class);
-
- Mockito.when(cryptor.fileNameCryptor()).thenReturn(filenameCryptor);
- Mockito.when(testFile.getParent()).thenReturn(testDir);
- Mockito.when(testFile.getFileName()).thenReturn(testFileName);
- Mockito.when(testDir.resolve(Mockito.anyString())).then(this::resolveChildOfTestDir);
- Mockito.when(testFile.resolveSibling(Mockito.anyString())).then(this::resolveChildOfTestDir);
- Mockito.when(testFile.getFileSystem()).thenReturn(testFileSystem);
- Mockito.when(testFileSystem.provider()).thenReturn(testFileSystemProvider);
- }
-
- private Path resolveChildOfTestDir(InvocationOnMock invocation) {
- Path result = Mockito.mock(Path.class);
- Path resultName = Mockito.mock(Path.class);
- Mockito.when(result.getFileName()).thenReturn(resultName);
- Mockito.when(resultName.toString()).thenReturn(invocation.getArgument(0));
- Mockito.when(result.getParent()).thenReturn(testDir);
- Mockito.when(result.getFileSystem()).thenReturn(testFileSystem);
- Mockito.when(result.resolveSibling(Mockito.anyString())).then(this::resolveChildOfTestDir);
- return result;
- }
-
- private ArgumentMatcher hasFileName(String name) {
- return path -> {
- if (path == null) {
- return false;
- }
- Path filename = path.getFileName();
- assert filename != null;
- return filename.toString().equals(name);
- };
- }
-
- private Answer fillBufferWithBytes(byte[] bytes) {
- return invocation -> {
- ByteBuffer buffer = invocation.getArgument(0);
- buffer.put(bytes);
- return bytes.length;
- };
- }
-
- @Test
- public void testPassthroughValidBase32NormalFile() throws IOException {
- Mockito.when(testFileName.toString()).thenReturn("ABCDEF==");
- Path resolved = conflictResolver.resolveConflictsIfNecessary(testFile, dirId);
- Mockito.verifyNoMoreInteractions(filenameCryptor);
- Mockito.verifyNoMoreInteractions(longFileNameProvider);
- Assertions.assertEquals(testFile.getFileName().toString(), resolved.getFileName().toString());
- }
-
- @Test
- public void testPassthroughInvalidBase32NormalFile() throws IOException {
- Mockito.when(testFileName.toString()).thenReturn("ABCDEF== (1)");
- Mockito.when(filenameCryptor.decryptFilename(Mockito.eq("ABCDEF=="), Mockito.any())).thenThrow(new AuthenticationFailedException("invalid ciphertext"));
- Path resolved = conflictResolver.resolveConflictsIfNecessary(testFile, dirId);
- Assertions.assertSame(testFile, resolved);
- }
-
- @Test
- public void testPassthroughValidBase32LongFile() throws IOException {
- Mockito.when(testFileName.toString()).thenReturn("ABCDEF==.lng");
- Path resolved = conflictResolver.resolveConflictsIfNecessary(testFile, dirId);
- Mockito.verifyNoMoreInteractions(filenameCryptor);
- Mockito.verifyNoMoreInteractions(longFileNameProvider);
- Assertions.assertEquals(testFile.getFileName().toString(), resolved.getFileName().toString());
- }
-
- @ParameterizedTest
- @ValueSource(strings = {"ABCDEF== (1)", "conflict_ABCDEF=="})
- public void testRenameNormalFile(String conflictingFileName) throws IOException {
- String ciphertextName = "ABCDEFGH2345====";
- Mockito.when(testFileName.toString()).thenReturn(conflictingFileName);
- Mockito.when(filenameCryptor.decryptFilename(Mockito.eq("ABCDEF=="), Mockito.any())).thenReturn("abcdef");
- Mockito.when(filenameCryptor.encryptFilename(Mockito.startsWith("abcdef ("), Mockito.any())).thenReturn(ciphertextName);
- Mockito.doThrow(new NoSuchFileException(ciphertextName)).when(testFileSystemProvider).checkAccess(Mockito.argThat(hasFileName(ciphertextName)));
- Path resolved = conflictResolver.resolveConflictsIfNecessary(testFile, dirId);
- Mockito.verify(testFileSystemProvider).move(Mockito.argThat(hasFileName(conflictingFileName)), Mockito.argThat(hasFileName(ciphertextName)), Mockito.any());
- Assertions.assertEquals(ciphertextName, resolved.getFileName().toString());
- }
-
- @ParameterizedTest
- @ValueSource(strings = {"ABCDEF== (1).lng", "conflict_ABCDEF==.lng"})
- public void testRenameLongFile(String conflictingFileName) throws IOException {
- String longCiphertextName = "ABCDEFGHABCDEFGHABCDEFGHABCDEFGHABCDEFGHABCDEFGHABCDEFGHABCDEFGHABCDEFGHABCDEFGHABCDEFGHABCDEFGHABCDEFGHABCDEFGHABCDEFGHABCDEFGH2345====";
- assert longCiphertextName.length() > Constants.SHORT_NAMES_MAX_LENGTH;
- Mockito.when(testFileName.toString()).thenReturn(conflictingFileName);
- Mockito.when(longFileNameProvider.inflate("ABCDEF==.lng")).thenReturn("FEDCBA==");
- Mockito.when(longFileNameProvider.deflate(longCiphertextName)).thenReturn("FEDCBA==.lng");
- Mockito.when(longFileNameProvider.isDeflated(conflictingFileName)).thenReturn(true);
- Mockito.when(filenameCryptor.decryptFilename(Mockito.eq("FEDCBA=="), Mockito.any())).thenReturn("fedcba");
- Mockito.when(filenameCryptor.encryptFilename(Mockito.startsWith("fedcba ("), Mockito.any())).thenReturn(longCiphertextName);
- Mockito.doThrow(new NoSuchFileException("FEDCBA==.lng")).when(testFileSystemProvider).checkAccess(Mockito.argThat(hasFileName("FEDCBA==.lng")));
- Path resolved = conflictResolver.resolveConflictsIfNecessary(testFile, dirId);
- Mockito.verify(longFileNameProvider).deflate(longCiphertextName);
- Mockito.verify(testFileSystemProvider).move(Mockito.argThat(hasFileName(conflictingFileName)), Mockito.argThat(hasFileName("FEDCBA==.lng")), Mockito.any());
- Assertions.assertEquals("FEDCBA==.lng", resolved.getFileName().toString());
- }
-
- @ParameterizedTest
- @ValueSource(strings = {"0ABCDEF== (1)", "conflict_0ABCDEF=="})
- public void testSilentlyDeleteConflictingDirectoryFileIdenticalToCanonicalFile(String conflictingFileName) throws IOException, ReflectiveOperationException {
- Mockito.when(testFileName.toString()).thenReturn(conflictingFileName);
- FileChannel canonicalFc = Mockito.mock(FileChannel.class);
- FileChannel conflictingFc = Mockito.mock(FileChannel.class);
- Field channelCloseLockField = AbstractInterruptibleChannel.class.getDeclaredField("closeLock");
- channelCloseLockField.setAccessible(true);
- channelCloseLockField.set(canonicalFc, new Object());
- channelCloseLockField.set(conflictingFc, new Object());
- Mockito.when(testFileSystemProvider.newByteChannel(Mockito.argThat(hasFileName("0ABCDEF==")), Mockito.any(), Mockito.any())).thenReturn(canonicalFc);
- Mockito.when(testFileSystemProvider.newByteChannel(Mockito.argThat(hasFileName(conflictingFileName)), Mockito.any(), Mockito.any())).thenReturn(conflictingFc);
- Mockito.when(canonicalFc.read(Mockito.any(ByteBuffer.class))).then(fillBufferWithBytes("12345".getBytes()));
- Mockito.when(conflictingFc.read(Mockito.any(ByteBuffer.class))).then(fillBufferWithBytes("12345".getBytes()));
- Path resolved = conflictResolver.resolveConflictsIfNecessary(testFile, dirId);
- Mockito.verify(testFileSystemProvider).deleteIfExists(Mockito.argThat(hasFileName(conflictingFileName)));
- Assertions.assertEquals("0ABCDEF==", resolved.getFileName().toString());
- }
-
- @ParameterizedTest
- @ValueSource(strings = {"0ABCDEF== (1)", "conflict_0ABCDEF=="})
- public void testSilentlyRenameConflictingDirectoryFileWithMissingCanonicalFile(String conflictingFileName) throws IOException {
- Mockito.when(testFileName.toString()).thenReturn(conflictingFileName);
- Mockito.doThrow(new NoSuchFileException("0ABCDEF==")).when(testFileSystemProvider).checkAccess(Mockito.argThat(hasFileName("0ABCDEF==")));
- Path resolved = conflictResolver.resolveConflictsIfNecessary(testFile, dirId);
- Mockito.verify(testFileSystemProvider).move(Mockito.argThat(hasFileName(conflictingFileName)), Mockito.argThat(hasFileName("0ABCDEF==")), Mockito.any());
- Assertions.assertEquals("0ABCDEF==", resolved.getFileName().toString());
- }
-
- @ParameterizedTest
- @ValueSource(strings = {"0ABCDEF== (1)", "conflict_0ABCDEF=="})
- public void testRenameDirectoryFile(String conflictingFileName) throws IOException, ReflectiveOperationException {
- Mockito.when(testFileName.toString()).thenReturn(conflictingFileName);
- FileChannel canonicalFc = Mockito.mock(FileChannel.class);
- FileChannel conflictingFc = Mockito.mock(FileChannel.class);
- Field channelCloseLockField = AbstractInterruptibleChannel.class.getDeclaredField("closeLock");
- channelCloseLockField.setAccessible(true);
- channelCloseLockField.set(canonicalFc, new Object());
- channelCloseLockField.set(conflictingFc, new Object());
- Mockito.when(testFileSystemProvider.newByteChannel(Mockito.argThat(hasFileName("0ABCDEF==")), Mockito.any(), Mockito.any())).thenReturn(canonicalFc);
- Mockito.when(testFileSystemProvider.newByteChannel(Mockito.argThat(hasFileName(conflictingFileName)), Mockito.any(), Mockito.any())).thenReturn(conflictingFc);
- Mockito.when(canonicalFc.read(Mockito.any(ByteBuffer.class))).then(fillBufferWithBytes("12345".getBytes()));
- Mockito.when(conflictingFc.read(Mockito.any(ByteBuffer.class))).then(fillBufferWithBytes("67890".getBytes()));
- String ciphertext = "ABCDEFGH2345====";
- String ciphertextName = "0" + ciphertext;
- Mockito.when(filenameCryptor.decryptFilename(Mockito.eq("ABCDEF=="), Mockito.any())).thenReturn("abcdef");
- Mockito.when(filenameCryptor.encryptFilename(Mockito.startsWith("abcdef ("), Mockito.any())).thenReturn(ciphertext);
- Mockito.doThrow(new NoSuchFileException(ciphertextName)).when(testFileSystemProvider).checkAccess(Mockito.argThat(hasFileName(ciphertextName)));
- Path resolved = conflictResolver.resolveConflictsIfNecessary(testFile, dirId);
- Mockito.verify(testFileSystemProvider).move(Mockito.argThat(hasFileName(conflictingFileName)), Mockito.argThat(hasFileName(ciphertextName)), Mockito.any());
- Assertions.assertEquals(ciphertextName, resolved.getFileName().toString());
- }
-
-}
diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoDirectoryStreamIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoDirectoryStreamIntegrationTest.java
deleted file mode 100644
index d45adcc7..00000000
--- a/src/test/java/org/cryptomator/cryptofs/CryptoDirectoryStreamIntegrationTest.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*******************************************************************************
- * 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;
-
-import com.google.common.jimfs.Jimfs;
-import org.cryptomator.cryptofs.CryptoDirectoryStream.ProcessedPaths;
-import org.cryptomator.cryptofs.CryptoPathMapper.CiphertextDirectory;
-import org.hamcrest.MatcherAssert;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import java.io.IOException;
-import java.nio.file.FileSystem;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-
-import static org.cryptomator.cryptofs.Constants.SHORT_NAMES_MAX_LENGTH;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.nullValue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class CryptoDirectoryStreamIntegrationTest {
-
- private FileSystem fileSystem;
-
- private LongFileNameProvider longFileNameProvider = mock(LongFileNameProvider.class);
-
- private CryptoDirectoryStream inTest;
-
- @BeforeEach
- public void setup() throws IOException {
- fileSystem = Jimfs.newFileSystem();
-
- Path dir = fileSystem.getPath("crapDirDoNotUse");
- Files.createDirectory(dir);
- inTest = new CryptoDirectoryStream(new CiphertextDirectory("", dir), null, null, null, longFileNameProvider, null, null, null, null, null);
- }
-
- @Test
- public void testInflateIfNeededWithShortFilename() throws IOException {
- String filename = "abc";
- Path ciphertextPath = fileSystem.getPath(filename);
- Files.createFile(ciphertextPath);
- when(longFileNameProvider.isDeflated(filename)).thenReturn(false);
-
- ProcessedPaths paths = new ProcessedPaths(ciphertextPath);
-
- ProcessedPaths result = inTest.inflateIfNeeded(paths);
-
- MatcherAssert.assertThat(result.getCiphertextPath(), is(ciphertextPath));
- MatcherAssert.assertThat(result.getInflatedPath(), is(ciphertextPath));
- MatcherAssert.assertThat(result.getCleartextPath(), is(nullValue()));
- MatcherAssert.assertThat(Files.exists(ciphertextPath), is(true));
- }
-
- @Test
- public void testInflateIfNeededWithRegularLongFilename() throws IOException {
- String filename = "abc";
- String inflatedName = IntStream.range(0, SHORT_NAMES_MAX_LENGTH + 1).mapToObj(ignored -> "a").collect(Collectors.joining());
- Path ciphertextPath = fileSystem.getPath(filename);
- Files.createFile(ciphertextPath);
- Path inflatedPath = fileSystem.getPath(inflatedName);
- when(longFileNameProvider.isDeflated(filename)).thenReturn(true);
- when(longFileNameProvider.inflate(filename)).thenReturn(inflatedName);
-
- ProcessedPaths paths = new ProcessedPaths(ciphertextPath);
-
- ProcessedPaths result = inTest.inflateIfNeeded(paths);
-
- MatcherAssert.assertThat(result.getCiphertextPath(), is(ciphertextPath));
- MatcherAssert.assertThat(result.getInflatedPath(), is(inflatedPath));
- MatcherAssert.assertThat(result.getCleartextPath(), is(nullValue()));
- MatcherAssert.assertThat(Files.exists(ciphertextPath), is(true));
- MatcherAssert.assertThat(Files.exists(inflatedPath), is(false));
- }
-
- @Test
- public void testInflateIfNeededWithLongFilenameThatShouldActuallyBeShort() throws IOException {
- String filename = "abc";
- String inflatedName = IntStream.range(0, SHORT_NAMES_MAX_LENGTH).mapToObj(ignored -> "a").collect(Collectors.joining());
- Path ciphertextPath = fileSystem.getPath(filename);
- Files.createFile(ciphertextPath);
- Path inflatedPath = fileSystem.getPath(inflatedName);
- when(longFileNameProvider.isDeflated(filename)).thenReturn(true);
- when(longFileNameProvider.inflate(filename)).thenReturn(inflatedName);
-
- ProcessedPaths paths = new ProcessedPaths(ciphertextPath);
-
- ProcessedPaths result = inTest.inflateIfNeeded(paths);
-
- MatcherAssert.assertThat(result.getCiphertextPath(), is(inflatedPath));
- MatcherAssert.assertThat(result.getInflatedPath(), is(inflatedPath));
- MatcherAssert.assertThat(result.getCleartextPath(), is(nullValue()));
- MatcherAssert.assertThat(Files.exists(ciphertextPath), is(false));
- MatcherAssert.assertThat(Files.exists(inflatedPath), is(true));
- }
-
-}
diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoDirectoryStreamTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoDirectoryStreamTest.java
deleted file mode 100644
index f187d166..00000000
--- a/src/test/java/org/cryptomator/cryptofs/CryptoDirectoryStreamTest.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*******************************************************************************
- * 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;
-
-import org.cryptomator.cryptofs.CryptoPathMapper.CiphertextDirectory;
-import org.cryptomator.cryptofs.mocks.NullSecureRandom;
-import org.cryptomator.cryptolib.Cryptors;
-import org.cryptomator.cryptolib.api.CryptorProvider;
-import org.cryptomator.cryptolib.api.FileNameCryptor;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.mockito.Mockito;
-
-import java.io.IOException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.DirectoryStream.Filter;
-import java.nio.file.FileSystem;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.nio.file.spi.FileSystemProvider;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.NoSuchElementException;
-import java.util.Spliterators;
-import java.util.function.Consumer;
-
-import static org.mockito.AdditionalAnswers.returnsFirstArg;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-
-public class CryptoDirectoryStreamTest {
-
- private static final Consumer DO_NOTHING_ON_CLOSE = ignored -> {
- };
- private static final Filter super Path> ACCEPT_ALL = ignored -> true;
- private static CryptorProvider CRYPTOR_PROVIDER = Cryptors.version1(NullSecureRandom.INSTANCE);
-
- private FileNameCryptor filenameCryptor;
- private Path ciphertextDirPath;
- private DirectoryStream dirStream;
- private CryptoPathMapper cryptoPathMapper;
- private LongFileNameProvider longFileNameProvider;
- private ConflictResolver conflictResolver;
- private FinallyUtil finallyUtil;
- private EncryptedNamePattern encryptedNamePattern = new EncryptedNamePattern();
-
- @BeforeEach
- @SuppressWarnings("unchecked")
- public void setup() throws IOException {
- filenameCryptor = CRYPTOR_PROVIDER.createNew().fileNameCryptor();
-
- ciphertextDirPath = Mockito.mock(Path.class);
- FileSystem fs = Mockito.mock(FileSystem.class);
- Mockito.when(ciphertextDirPath.getFileSystem()).thenReturn(fs);
- FileSystemProvider provider = Mockito.mock(FileSystemProvider.class);
- Mockito.when(fs.provider()).thenReturn(provider);
- dirStream = Mockito.mock(DirectoryStream.class);
- Mockito.when(provider.newDirectoryStream(Mockito.same(ciphertextDirPath), Mockito.any())).thenReturn(dirStream);
- longFileNameProvider = Mockito.mock(LongFileNameProvider.class);
- conflictResolver = Mockito.mock(ConflictResolver.class);
- finallyUtil = mock(FinallyUtil.class);
- Mockito.when(longFileNameProvider.inflate(Mockito.anyString())).then(invocation -> {
- String shortName = invocation.getArgument(0);
- if (shortName.contains("invalid")) {
- throw new IOException("invalid shortened name");
- } else {
- return StringUtils.removeEnd(shortName, ".lng");
- }
- });
- cryptoPathMapper = Mockito.mock(CryptoPathMapper.class);
- Mockito.when(cryptoPathMapper.resolveDirectory(Mockito.any())).then(invocation -> {
- Path dirFilePath = invocation.getArgument(0);
- if (dirFilePath.toString().contains("invalid")) {
- throw new IOException("Invalid directory.");
- }
- Path dirPath = Mockito.mock(Path.class);
- BasicFileAttributes attrs = Mockito.mock(BasicFileAttributes.class);
- Mockito.when(dirPath.getFileSystem()).thenReturn(fs);
- Mockito.when(provider.readAttributes(dirPath, BasicFileAttributes.class)).thenReturn(attrs);
- Mockito.when(attrs.isDirectory()).thenReturn(!dirFilePath.toString().contains("noDirectory"));
- return new CiphertextDirectory("asdf", dirPath);
- });
-
- Mockito.when(conflictResolver.resolveConflictsIfNecessary(Mockito.any(), Mockito.any())).then(returnsFirstArg());
-
- doAnswer(invocation -> {
- for (Object runnable : invocation.getArguments()) {
- ((RunnableThrowingException>) runnable).run();
- }
- return null;
- }).when(finallyUtil).guaranteeInvocationOf(any(RunnableThrowingException.class), any(RunnableThrowingException.class), any(RunnableThrowingException.class));
- }
-
- @Test
- public void testDirListing() throws IOException {
- Path cleartextPath = Paths.get("/foo/bar");
-
- List ciphertextFileNames = new ArrayList<>();
- ciphertextFileNames.add(filenameCryptor.encryptFilename("one", "foo".getBytes()));
- ciphertextFileNames.add(filenameCryptor.encryptFilename("two", "foo".getBytes()) + "_conflict");
- ciphertextFileNames.add("0" + filenameCryptor.encryptFilename("three", "foo".getBytes()));
- ciphertextFileNames.add("0invalidDirectory");
- ciphertextFileNames.add("0noDirectory");
- ciphertextFileNames.add("invalidLongName.lng");
- ciphertextFileNames.add(filenameCryptor.encryptFilename("four", "foo".getBytes()) + ".lng");
- ciphertextFileNames.add(filenameCryptor.encryptFilename("invalid", "bar".getBytes()));
- ciphertextFileNames.add("alsoInvalid");
- Mockito.when(dirStream.spliterator()).thenReturn(ciphertextFileNames.stream().map(cleartextPath::resolve).spliterator());
-
- try (CryptoDirectoryStream stream = new CryptoDirectoryStream(new CiphertextDirectory("foo", ciphertextDirPath), cleartextPath, filenameCryptor, cryptoPathMapper, longFileNameProvider, conflictResolver, ACCEPT_ALL,
- DO_NOTHING_ON_CLOSE, finallyUtil, encryptedNamePattern)) {
- Iterator iter = stream.iterator();
- Assertions.assertTrue(iter.hasNext());
- Assertions.assertEquals(cleartextPath.resolve("one"), iter.next());
- Assertions.assertTrue(iter.hasNext());
- Assertions.assertEquals(cleartextPath.resolve("two"), iter.next());
- Assertions.assertTrue(iter.hasNext());
- Assertions.assertEquals(cleartextPath.resolve("three"), iter.next());
- Assertions.assertTrue(iter.hasNext());
- Assertions.assertEquals(cleartextPath.resolve("four"), iter.next());
- Assertions.assertFalse(iter.hasNext());
- Mockito.verify(dirStream, Mockito.never()).close();
- }
- Mockito.verify(dirStream).close();
- }
-
- @Test
- public void testDirListingForEmptyDir() throws IOException {
- Path cleartextPath = Paths.get("/foo/bar");
-
- Mockito.when(dirStream.spliterator()).thenReturn(Spliterators.emptySpliterator());
-
- try (CryptoDirectoryStream stream = new CryptoDirectoryStream(new CiphertextDirectory("foo", ciphertextDirPath), cleartextPath, filenameCryptor, cryptoPathMapper, longFileNameProvider, conflictResolver, ACCEPT_ALL,
- DO_NOTHING_ON_CLOSE, finallyUtil, encryptedNamePattern)) {
- Iterator iter = stream.iterator();
- Assertions.assertFalse(iter.hasNext());
- Assertions.assertThrows(NoSuchElementException.class, () -> {
- iter.next();
- });
- }
- }
-
-}
diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java
index 1dd145cd..f75e436d 100644
--- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java
@@ -5,6 +5,11 @@
import org.cryptomator.cryptofs.attr.AttributeProvider;
import org.cryptomator.cryptofs.attr.AttributeViewProvider;
import org.cryptomator.cryptofs.attr.AttributeViewType;
+import org.cryptomator.cryptofs.common.CiphertextFileType;
+import org.cryptomator.cryptofs.common.FinallyUtil;
+import org.cryptomator.cryptofs.common.RunnableThrowingException;
+import org.cryptomator.cryptofs.dir.CiphertextDirectoryDeleter;
+import org.cryptomator.cryptofs.dir.DirectoryStreamFactory;
import org.cryptomator.cryptofs.fh.OpenCryptoFile;
import org.cryptomator.cryptofs.fh.OpenCryptoFiles;
import org.cryptomator.cryptofs.fh.OpenCryptoFiles.TwoPhaseMove;
@@ -14,11 +19,11 @@
import org.hamcrest.MatcherAssert;
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.mockito.Mockito;
-import javax.inject.Named;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
@@ -51,10 +56,10 @@
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.UserPrincipal;
import java.nio.file.spi.FileSystemProvider;
+import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Iterator;
-import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -83,7 +88,6 @@ public class CryptoFileSystemImplTest {
private final OpenCryptoFiles openCryptoFiles = mock(OpenCryptoFiles.class);
private final Symlinks symlinks = mock(Symlinks.class);
private final CryptoPathMapper cryptoPathMapper = mock(CryptoPathMapper.class);
- private final LongFileNameProvider longFileNameProvider = Mockito.mock(LongFileNameProvider.class);
private final DirectoryIdProvider dirIdProvider = mock(DirectoryIdProvider.class);
private final AttributeProvider fileAttributeProvider = mock(AttributeProvider.class);
private final AttributeByNameProvider fileAttributeByNameProvider = mock(AttributeByNameProvider.class);
@@ -108,7 +112,7 @@ public void setup() {
when(cryptoPathFactory.emptyFor(any())).thenReturn(empty);
inTest = new CryptoFileSystemImpl(provider, cryptoFileSystems, pathToVault, cryptor,
- fileStore, stats, cryptoPathMapper, longFileNameProvider, cryptoPathFactory,
+ fileStore, stats, cryptoPathMapper, cryptoPathFactory,
pathMatcherFactory, directoryStreamFactory, dirIdProvider,
fileAttributeProvider, fileAttributeByNameProvider, fileAttributeViewProvider,
openCryptoFiles, symlinks, finallyUtil, ciphertextDirDeleter, readonlyFlag, rootDirectoryInitializer);
@@ -336,101 +340,118 @@ public void testNewWatchServiceThrowsUnsupportedOperationException() throws IOEx
inTest.newWatchService();
});
}
-
+
@Nested
public class NewFileChannel {
-
+
private final CryptoPath cleartextPath = mock(CryptoPath.class, "cleartext");
- private final Path ciphertextPath = mock(Path.class, "ciphertext");
+ private final CryptoPath ciphertextFilePath = mock(CryptoPath.class, "ciphertext");
+ private final CiphertextFilePath ciphertextPath = mock(CiphertextFilePath.class);
private final OpenCryptoFile openCryptoFile = mock(OpenCryptoFile.class);
private final FileChannel fileChannel = mock(FileChannel.class);
@BeforeEach
public void setup() throws IOException {
when(cryptoPathMapper.getCiphertextFileType(cleartextPath)).thenReturn(CiphertextFileType.FILE);
- when(cryptoPathMapper.getCiphertextFilePath(cleartextPath, CiphertextFileType.FILE)).thenReturn(ciphertextPath);
- when(openCryptoFiles.getOrCreate(ciphertextPath)).thenReturn(openCryptoFile);
+ when(cryptoPathMapper.getCiphertextFilePath(cleartextPath)).thenReturn(ciphertextPath);
+ when(ciphertextPath.getFilePath()).thenReturn(ciphertextFilePath);
+ when(openCryptoFiles.getOrCreate(ciphertextFilePath)).thenReturn(openCryptoFile);
when(openCryptoFile.newFileChannel(any())).thenReturn(fileChannel);
}
-
+
@Test
- @Named("newFileChannel read-only")
+ @DisplayName("newFileChannel read-only")
public void testNewFileChannelReadOnly() throws IOException {
FileChannel ch = inTest.newFileChannel(cleartextPath, EnumSet.of(StandardOpenOption.READ));
-
+
Assertions.assertSame(fileChannel, ch);
verify(readonlyFlag, Mockito.never()).assertWritable();
}
@Test
- @Named("newFileChannel read-only with long filename")
+ @DisplayName("newFileChannel read-only with long filename")
public void testNewFileChannelReadOnlyShortened() throws IOException {
- LongFileNameProvider.DeflatedFileName deflatedFileName = Mockito.mock(LongFileNameProvider.DeflatedFileName.class);
- when(longFileNameProvider.getCached(ciphertextPath)).thenReturn(Optional.of(deflatedFileName));
-
FileChannel ch = inTest.newFileChannel(cleartextPath, EnumSet.of(StandardOpenOption.READ));
Assertions.assertSame(fileChannel, ch);
verify(readonlyFlag, Mockito.never()).assertWritable();
- verify(deflatedFileName, Mockito.never()).persist();
+ verify(ciphertextPath, Mockito.never()).persistLongFileName();
}
@Test
- @Named("newFileChannel read-write with long filename")
+ @DisplayName("newFileChannel read-write with long filename")
public void testNewFileChannelReadWriteShortened() throws IOException {
- LongFileNameProvider.DeflatedFileName deflatedFileName = Mockito.mock(LongFileNameProvider.DeflatedFileName.class);
- when(longFileNameProvider.getCached(ciphertextPath)).thenReturn(Optional.of(deflatedFileName));
-
FileChannel ch = inTest.newFileChannel(cleartextPath, EnumSet.of(StandardOpenOption.WRITE));
Assertions.assertSame(fileChannel, ch);
verify(readonlyFlag, Mockito.atLeastOnce()).assertWritable();
- verify(deflatedFileName).persist();
+ verify(ciphertextPath).persistLongFileName();
}
-
+
}
@Nested
public class Delete {
private final CryptoPath cleartextPath = mock(CryptoPath.class, "cleartext");
- private final Path ciphertextFilePath = mock(Path.class, "ciphertextFile");
- private final Path ciphertextDirFilePath = mock(Path.class, "ciphertextDirFile");
- private final Path ciphertextDirPath = mock(Path.class, "ciphertextDir");
+ private final Path ciphertextRawPath = mock(Path.class, "d/00/00/path.c9r");
+ private final Path ciphertextDirFilePath = mock(Path.class, "d/00/00/path.c9r/dir.c9r");
+ private final Path ciphertextDirPath = mock(Path.class, "d/FF/FF/");
+ private final CiphertextFilePath ciphertextPath = mock(CiphertextFilePath.class, "ciphertext");
private final FileSystem physicalFs = mock(FileSystem.class);
private final FileSystemProvider physicalFsProv = mock(FileSystemProvider.class);
+ private final BasicFileAttributes ciphertextPathAttr = mock(BasicFileAttributes.class);
+ private final BasicFileAttributes ciphertextDirFilePathAttr = mock(BasicFileAttributes.class);
@BeforeEach
public void setup() throws IOException {
- when(ciphertextFilePath.getFileSystem()).thenReturn(physicalFs);
- when(ciphertextDirFilePath.getFileSystem()).thenReturn(physicalFs);
- when(ciphertextDirPath.getFileSystem()).thenReturn(physicalFs);
when(physicalFs.provider()).thenReturn(physicalFsProv);
- when(cryptoPathMapper.getCiphertextFilePath(cleartextPath, CiphertextFileType.FILE)).thenReturn(ciphertextFilePath);
- when(cryptoPathMapper.getCiphertextFilePath(cleartextPath, CiphertextFileType.DIRECTORY)).thenReturn(ciphertextDirFilePath);
+ when(ciphertextRawPath.getFileSystem()).thenReturn(physicalFs);
+ when(ciphertextDirPath.getFileSystem()).thenReturn(physicalFs);
+ when(ciphertextDirFilePath.getFileSystem()).thenReturn(physicalFs);
+ when(ciphertextRawPath.resolve("dir.c9r")).thenReturn(ciphertextDirFilePath);
+ when(cryptoPathMapper.getCiphertextFilePath(cleartextPath)).thenReturn(ciphertextPath);
+ when(ciphertextPath.getRawPath()).thenReturn(ciphertextRawPath);
+ when(ciphertextPath.getDirFilePath()).thenReturn(ciphertextDirFilePath);
when(cryptoPathMapper.getCiphertextDir(cleartextPath)).thenReturn(new CiphertextDirectory("foo", ciphertextDirPath));
+ when(physicalFsProv.readAttributes(ciphertextRawPath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenReturn(ciphertextPathAttr);
+ when(physicalFsProv.readAttributes(ciphertextDirFilePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenReturn(ciphertextDirFilePathAttr);
+
}
@Test
public void testDeleteExistingFile() throws IOException {
when(cryptoPathMapper.getCiphertextFileType(cleartextPath)).thenReturn(CiphertextFileType.FILE);
- when(physicalFsProv.deleteIfExists(ciphertextFilePath)).thenReturn(true);
+ when(physicalFsProv.deleteIfExists(ciphertextRawPath)).thenReturn(true);
inTest.delete(cleartextPath);
verify(readonlyFlag).assertWritable();
- verify(physicalFsProv).deleteIfExists(ciphertextFilePath);
+ verify(physicalFsProv).deleteIfExists(ciphertextRawPath);
}
@Test
public void testDeleteExistingDirectory() throws IOException {
when(cryptoPathMapper.getCiphertextFileType(cleartextPath)).thenReturn(CiphertextFileType.DIRECTORY);
- when(physicalFsProv.deleteIfExists(ciphertextFilePath)).thenReturn(false);
+ when(physicalFsProv.deleteIfExists(ciphertextRawPath)).thenReturn(false);
+ when(ciphertextPathAttr.isDirectory()).thenReturn(true);
+ when(physicalFsProv.newDirectoryStream(Mockito.eq(ciphertextRawPath), Mockito.any())).thenReturn(new DirectoryStream() {
+ @Override
+ public Iterator iterator() {
+ return Arrays.asList(ciphertextDirFilePath).iterator();
+ }
+
+ @Override
+ public void close() {
+ // no-op
+ }
+ });
inTest.delete(cleartextPath);
verify(ciphertextDirDeleter).deleteCiphertextDirIncludingNonCiphertextFiles(ciphertextDirPath, cleartextPath);
verify(readonlyFlag).assertWritable();
verify(physicalFsProv).deleteIfExists(ciphertextDirFilePath);
+ verify(physicalFsProv).deleteIfExists(ciphertextRawPath);
verify(dirIdProvider).delete(ciphertextDirFilePath);
verify(cryptoPathMapper).invalidatePathMapping(cleartextPath);
}
@@ -447,7 +468,7 @@ public void testDeleteNonExistingFileOrDir() throws IOException {
@Test
public void testDeleteNonEmptyDir() throws IOException {
when(cryptoPathMapper.getCiphertextFileType(cleartextPath)).thenReturn(CiphertextFileType.DIRECTORY);
- when(physicalFsProv.deleteIfExists(ciphertextFilePath)).thenReturn(false);
+ when(physicalFsProv.deleteIfExists(ciphertextRawPath)).thenReturn(false);
Mockito.doThrow(new DirectoryNotEmptyException("ciphertextDir")).when(ciphertextDirDeleter).deleteCiphertextDirIncludingNonCiphertextFiles(ciphertextDirPath, cleartextPath);
Assertions.assertThrows(DirectoryNotEmptyException.class, () -> {
@@ -464,38 +485,47 @@ public class CopyAndMove {
private final CryptoPath sourceLinkTarget = mock(CryptoPath.class, "sourceLinkTarget");
private final CryptoPath cleartextDestination = mock(CryptoPath.class, "cleartextDestination");
private final CryptoPath destinationLinkTarget = mock(CryptoPath.class, "destinationLinkTarget");
- private final Path ciphertextSourceFile = mock(Path.class, "ciphertextSourceFile");
- private final Path ciphertextSourceDirFile = mock(Path.class, "ciphertextSourceDirFile");
- private final Path ciphertextSourceDir = mock(Path.class, "ciphertextSourceDir");
- private final Path ciphertextDestinationFile = mock(Path.class, "ciphertextDestinationFile");
- private final Path ciphertextDestinationDirFile = mock(Path.class, "ciphertextDestinationDirFile");
- private final Path ciphertextDestinationDir = mock(Path.class, "ciphertextDestinationDir");
+ private final CiphertextFilePath ciphertextSource = mock(CiphertextFilePath.class, "ciphertextSource");
+ private final CiphertextFilePath ciphertextDestination = mock(CiphertextFilePath.class, "ciphertextDestination");
+ private final Path ciphertextSourceFile = mock(Path.class, "d/00/00/source.c9r");
+ private final Path ciphertextSourceDirFile = mock(Path.class, "d/00/00/source.c9r/dir.c9r");
+ private final Path ciphertextSourceDir = mock(Path.class, "d/00/SOURCE/");
+ private final Path ciphertextDestinationFile = mock(Path.class, "d/00/00/dest.c9r");
+ private final Path ciphertextDestinationLongNameFile = mock(Path.class, "d/00/00/dest.c9r/name.c9s");
+ private final Path ciphertextDestinationDirFile = mock(Path.class, "d/00/00/dest.c9r/dir.c9r");
+ private final Path ciphertextDestinationDir = mock(Path.class, "d/00/DEST/");
private final FileSystem physicalFs = mock(FileSystem.class);
private final FileSystemProvider physicalFsProv = mock(FileSystemProvider.class);
@BeforeEach
public void setup() throws IOException {
+ when(ciphertextSource.getRawPath()).thenReturn(ciphertextSourceFile);
+ when(ciphertextSource.getFilePath()).thenReturn(ciphertextSourceFile);
+ when(ciphertextSource.getSymlinkFilePath()).thenReturn(ciphertextSourceFile);
+ when(ciphertextSource.getDirFilePath()).thenReturn(ciphertextSourceDirFile);
+ when(ciphertextDestination.getRawPath()).thenReturn(ciphertextDestinationFile);
+ when(ciphertextDestination.getFilePath()).thenReturn(ciphertextDestinationFile);
+ when(ciphertextDestination.getSymlinkFilePath()).thenReturn(ciphertextDestinationFile);
+ when(ciphertextDestination.getDirFilePath()).thenReturn(ciphertextDestinationDirFile);
+ when(ciphertextDestination.getInflatedNamePath()).thenReturn(ciphertextDestinationLongNameFile);
when(ciphertextSourceFile.getFileSystem()).thenReturn(physicalFs);
when(ciphertextSourceDirFile.getFileSystem()).thenReturn(physicalFs);
when(ciphertextSourceDir.getFileSystem()).thenReturn(physicalFs);
when(ciphertextDestinationFile.getFileSystem()).thenReturn(physicalFs);
+ when(ciphertextDestinationLongNameFile.getFileSystem()).thenReturn(physicalFs);
when(ciphertextDestinationDirFile.getFileSystem()).thenReturn(physicalFs);
when(ciphertextDestinationDir.getFileSystem()).thenReturn(physicalFs);
when(physicalFs.provider()).thenReturn(physicalFsProv);
- when(cryptoPathMapper.getCiphertextFilePath(cleartextSource, CiphertextFileType.FILE)).thenReturn(ciphertextSourceFile);
- when(cryptoPathMapper.getCiphertextFilePath(cleartextSource, CiphertextFileType.DIRECTORY)).thenReturn(ciphertextSourceDirFile);
- when(cryptoPathMapper.getCiphertextFilePath(cleartextSource, CiphertextFileType.SYMLINK)).thenReturn(ciphertextSourceFile);
- when(cryptoPathMapper.getCiphertextFilePath(cleartextDestination, CiphertextFileType.FILE)).thenReturn(ciphertextDestinationFile);
- when(cryptoPathMapper.getCiphertextFilePath(cleartextDestination, CiphertextFileType.DIRECTORY)).thenReturn(ciphertextDestinationDirFile);
- when(cryptoPathMapper.getCiphertextFilePath(cleartextDestination, CiphertextFileType.SYMLINK)).thenReturn(ciphertextDestinationFile);
+ when(cryptoPathMapper.getCiphertextFilePath(cleartextSource)).thenReturn(ciphertextSource);
+ when(cryptoPathMapper.getCiphertextFilePath(cleartextDestination)).thenReturn(ciphertextDestination);
when(cryptoPathMapper.getCiphertextDir(cleartextSource)).thenReturn(new CiphertextDirectory("foo", ciphertextSourceDir));
when(cryptoPathMapper.getCiphertextDir(cleartextDestination)).thenReturn(new CiphertextDirectory("bar", ciphertextDestinationDir));
when(symlinks.resolveRecursively(cleartextSource)).thenReturn(sourceLinkTarget);
when(symlinks.resolveRecursively(cleartextDestination)).thenReturn(destinationLinkTarget);
when(cryptoPathMapper.getCiphertextFileType(sourceLinkTarget)).thenReturn(CiphertextFileType.FILE);
when(cryptoPathMapper.getCiphertextFileType(destinationLinkTarget)).thenReturn(CiphertextFileType.FILE);
- when(cryptoPathMapper.getCiphertextFilePath(sourceLinkTarget, CiphertextFileType.FILE)).thenReturn(ciphertextSourceFile);
- when(cryptoPathMapper.getCiphertextFilePath(destinationLinkTarget, CiphertextFileType.FILE)).thenReturn(ciphertextDestinationFile);
+ when(cryptoPathMapper.getCiphertextFilePath(sourceLinkTarget)).thenReturn(ciphertextSource);
+ when(cryptoPathMapper.getCiphertextFilePath(destinationLinkTarget)).thenReturn(ciphertextDestination);
}
@Nested
@@ -573,9 +603,9 @@ public void moveDirectoryDontReplaceExisting() throws IOException {
inTest.move(cleartextSource, cleartextDestination, option1, option2);
verify(readonlyFlag).assertWritable();
- verify(physicalFsProv).move(ciphertextSourceDirFile, ciphertextDestinationDirFile, option1, option2);
+ verify(physicalFsProv).move(ciphertextSourceFile, ciphertextDestinationFile, option1, option2);
verify(dirIdProvider).move(ciphertextSourceDirFile, ciphertextDestinationDirFile);
- verify(cryptoPathMapper).invalidatePathMapping(cleartextSource);
+ verify(cryptoPathMapper).movePathMapping(cleartextSource, cleartextDestination);
}
@Test
@@ -583,8 +613,13 @@ public void moveDirectoryDontReplaceExisting() throws IOException {
public void moveDirectoryReplaceExisting() throws IOException {
when(cryptoPathMapper.getCiphertextFileType(cleartextSource)).thenReturn(CiphertextFileType.DIRECTORY);
when(cryptoPathMapper.getCiphertextFileType(cleartextDestination)).thenReturn(CiphertextFileType.DIRECTORY);
+ BasicFileAttributes dirAttr = mock(BasicFileAttributes.class);
+ when(physicalFsProv.readAttributes(Mockito.same(ciphertextDestinationFile), Mockito.same(BasicFileAttributes.class), Mockito.any())).thenReturn(dirAttr);
+ when(physicalFsProv.readAttributes(Mockito.same(ciphertextDestinationDir), Mockito.same(BasicFileAttributes.class), Mockito.any())).thenReturn(dirAttr);
+ when(dirAttr.isDirectory()).thenReturn(true);
DirectoryStream ds = mock(DirectoryStream.class);
Iterator iter = mock(Iterator.class);
+ when(physicalFsProv.newDirectoryStream(Mockito.same(ciphertextDestinationFile), Mockito.any())).thenReturn(ds);
when(physicalFsProv.newDirectoryStream(Mockito.same(ciphertextDestinationDir), Mockito.any())).thenReturn(ds);
when(ds.iterator()).thenReturn(iter);
when(iter.hasNext()).thenReturn(false);
@@ -592,10 +627,11 @@ public void moveDirectoryReplaceExisting() throws IOException {
inTest.move(cleartextSource, cleartextDestination, StandardCopyOption.REPLACE_EXISTING);
verify(readonlyFlag).assertWritable();
- verify(physicalFsProv).delete(ciphertextDestinationDir);
- verify(physicalFsProv).move(ciphertextSourceDirFile, ciphertextDestinationDirFile, StandardCopyOption.REPLACE_EXISTING);
+ verify(physicalFsProv).deleteIfExists(ciphertextDestinationDir);
+ verify(physicalFsProv).deleteIfExists(ciphertextDestinationFile);
+ verify(physicalFsProv).move(ciphertextSourceFile, ciphertextDestinationFile, StandardCopyOption.REPLACE_EXISTING);
verify(dirIdProvider).move(ciphertextSourceDirFile, ciphertextDestinationDirFile);
- verify(cryptoPathMapper).invalidatePathMapping(cleartextSource);
+ verify(cryptoPathMapper).movePathMapping(cleartextSource, cleartextDestination);
}
@Test
@@ -727,7 +763,7 @@ public void copyFile() throws IOException {
public void copyDirectory() throws IOException {
when(cryptoPathMapper.getCiphertextFileType(cleartextSource)).thenReturn(CiphertextFileType.DIRECTORY);
when(cryptoPathMapper.getCiphertextFileType(cleartextDestination)).thenThrow(NoSuchFileException.class);
- Mockito.doThrow(new NoSuchFileException("ciphertextDestinationDirFile")).when(physicalFsProv).checkAccess(ciphertextDestinationDirFile);
+ Mockito.doThrow(new NoSuchFileException("ciphertextDestinationDirFile")).when(physicalFsProv).checkAccess(ciphertextDestinationFile);
inTest.copy(cleartextSource, cleartextDestination);
@@ -762,7 +798,7 @@ public void copyDirectoryReplaceExisting() throws IOException {
public void moveDirectoryCopyBasicAttributes() throws IOException {
when(cryptoPathMapper.getCiphertextFileType(cleartextSource)).thenReturn(CiphertextFileType.DIRECTORY);
when(cryptoPathMapper.getCiphertextFileType(cleartextDestination)).thenThrow(NoSuchFileException.class);
- Mockito.doThrow(new NoSuchFileException("ciphertextDestinationDirFile")).when(physicalFsProv).checkAccess(ciphertextDestinationDirFile);
+ Mockito.doThrow(new NoSuchFileException("ciphertextDestinationDirFile")).when(physicalFsProv).checkAccess(ciphertextDestinationFile);
when(fileStore.supportedFileAttributeViewTypes()).thenReturn(EnumSet.of(AttributeViewType.BASIC));
FileTime lastModifiedTime = FileTime.from(1, TimeUnit.HOURS);
FileTime lastAccessTime = FileTime.from(2, TimeUnit.HOURS);
@@ -785,7 +821,7 @@ public void moveDirectoryCopyBasicAttributes() throws IOException {
public void moveDirectoryCopyFileOwnerAttributes() throws IOException {
when(cryptoPathMapper.getCiphertextFileType(cleartextSource)).thenReturn(CiphertextFileType.DIRECTORY);
when(cryptoPathMapper.getCiphertextFileType(cleartextDestination)).thenThrow(NoSuchFileException.class);
- Mockito.doThrow(new NoSuchFileException("ciphertextDestinationDirFile")).when(physicalFsProv).checkAccess(ciphertextDestinationDirFile);
+ Mockito.doThrow(new NoSuchFileException("ciphertextDestinationDirFile")).when(physicalFsProv).checkAccess(ciphertextDestinationFile);
when(fileStore.supportedFileAttributeViewTypes()).thenReturn(EnumSet.of(AttributeViewType.OWNER));
UserPrincipal owner = mock(UserPrincipal.class);
FileOwnerAttributeView srcAttrsView = mock(FileOwnerAttributeView.class);
@@ -805,7 +841,7 @@ public void moveDirectoryCopyFileOwnerAttributes() throws IOException {
public void moveDirectoryCopyPosixAttributes() throws IOException {
when(cryptoPathMapper.getCiphertextFileType(cleartextSource)).thenReturn(CiphertextFileType.DIRECTORY);
when(cryptoPathMapper.getCiphertextFileType(cleartextDestination)).thenThrow(NoSuchFileException.class);
- Mockito.doThrow(new NoSuchFileException("ciphertextDestinationDirFile")).when(physicalFsProv).checkAccess(ciphertextDestinationDirFile);
+ Mockito.doThrow(new NoSuchFileException("ciphertextDestinationDirFile")).when(physicalFsProv).checkAccess(ciphertextDestinationFile);
when(fileStore.supportedFileAttributeViewTypes()).thenReturn(EnumSet.of(AttributeViewType.POSIX));
GroupPrincipal group = mock(GroupPrincipal.class);
Set permissions = mock(Set.class);
@@ -827,7 +863,7 @@ public void moveDirectoryCopyPosixAttributes() throws IOException {
public void moveDirectoryCopyDosAttributes() throws IOException {
when(cryptoPathMapper.getCiphertextFileType(cleartextSource)).thenReturn(CiphertextFileType.DIRECTORY);
when(cryptoPathMapper.getCiphertextFileType(cleartextDestination)).thenThrow(NoSuchFileException.class);
- Mockito.doThrow(new NoSuchFileException("ciphertextDestinationDirFile")).when(physicalFsProv).checkAccess(ciphertextDestinationDirFile);
+ Mockito.doThrow(new NoSuchFileException("ciphertextDestinationDirFile")).when(physicalFsProv).checkAccess(ciphertextDestinationFile);
when(fileStore.supportedFileAttributeViewTypes()).thenReturn(EnumSet.of(AttributeViewType.DOS));
DosFileAttributes srcAttrs = mock(DosFileAttributes.class);
DosFileAttributeView dstAttrView = mock(DosFileAttributeView.class);
@@ -942,16 +978,22 @@ public void createDirectoryCreatesDirectoryIfConditonsAreMet() throws IOExceptio
CryptoPath path = mock(CryptoPath.class, "path");
CryptoPath parent = mock(CryptoPath.class, "parent");
Path ciphertextParent = mock(Path.class, "ciphertextParent");
- Path ciphertextDirFile = mock(Path.class, "ciphertextDirFile");
- Path ciphertextDirPath = mock(Path.class, "ciphertextDir");
+ Path ciphertextRawPath = mock(Path.class, "d/00/00/path.c9r");
+ Path ciphertextDirFile = mock(Path.class, "d/00/00/path.c9r/dir.c9r");
+ Path ciphertextDirPath = mock(Path.class, "d/FF/FF/");
+ CiphertextFilePath ciphertextPath = mock(CiphertextFilePath.class, "ciphertext");
String dirId = "DirId1234ABC";
FileChannelMock channel = new FileChannelMock(100);
when(path.getParent()).thenReturn(parent);
- when(cryptoPathMapper.getCiphertextFilePath(path, CiphertextFileType.DIRECTORY)).thenReturn(ciphertextDirFile);
+ when(ciphertextRawPath.resolve("dir.c9r")).thenReturn(ciphertextDirFile);
+ when(cryptoPathMapper.getCiphertextFilePath(path)).thenReturn(ciphertextPath);
when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CiphertextDirectory(dirId, ciphertextDirPath));
when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CiphertextDirectory("parentDirId", ciphertextDirPath));
when(cryptoPathMapper.getCiphertextFileType(path)).thenThrow(NoSuchFileException.class);
+ when(ciphertextPath.getRawPath()).thenReturn(ciphertextRawPath);
+ when(ciphertextPath.getDirFilePath()).thenReturn(ciphertextDirFile);
when(ciphertextParent.getFileSystem()).thenReturn(fileSystem);
+ when(ciphertextRawPath.getFileSystem()).thenReturn(fileSystem);
when(ciphertextDirFile.getFileSystem()).thenReturn(fileSystem);
when(ciphertextDirPath.getFileSystem()).thenReturn(fileSystem);
when(provider.newFileChannel(ciphertextDirFile, EnumSet.of(StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE))).thenReturn(channel);
@@ -967,16 +1009,22 @@ public void createDirectoryClearsDirIdAndDeletesDirFileIfCreatingDirFails() thro
CryptoPath path = mock(CryptoPath.class, "path");
CryptoPath parent = mock(CryptoPath.class, "parent");
Path ciphertextParent = mock(Path.class, "ciphertextParent");
- Path ciphertextDirFile = mock(Path.class, "ciphertextDirFile");
- Path ciphertextDirPath = mock(Path.class, "ciphertextDir");
+ Path ciphertextRawPath = mock(Path.class, "d/00/00/path.c9r");
+ Path ciphertextDirFile = mock(Path.class, "d/00/00/path.c9r/dir.c9r");
+ Path ciphertextDirPath = mock(Path.class, "d/FF/FF/");
+ CiphertextFilePath ciphertextPath = mock(CiphertextFilePath.class, "ciphertext");
String dirId = "DirId1234ABC";
FileChannelMock channel = new FileChannelMock(100);
when(path.getParent()).thenReturn(parent);
- when(cryptoPathMapper.getCiphertextFilePath(path, CiphertextFileType.DIRECTORY)).thenReturn(ciphertextDirFile);
+ when(ciphertextRawPath.resolve("dir.c9r")).thenReturn(ciphertextDirFile);
+ when(cryptoPathMapper.getCiphertextFilePath(path)).thenReturn(ciphertextPath);
when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CiphertextDirectory(dirId, ciphertextDirPath));
when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CiphertextDirectory("parentDirId", ciphertextDirPath));
when(cryptoPathMapper.getCiphertextFileType(path)).thenThrow(NoSuchFileException.class);
+ when(ciphertextPath.getRawPath()).thenReturn(ciphertextRawPath);
+ when(ciphertextPath.getDirFilePath()).thenReturn(ciphertextDirFile);
when(ciphertextParent.getFileSystem()).thenReturn(fileSystem);
+ when(ciphertextRawPath.getFileSystem()).thenReturn(fileSystem);
when(ciphertextDirFile.getFileSystem()).thenReturn(fileSystem);
when(ciphertextDirPath.getFileSystem()).thenReturn(fileSystem);
when(provider.newFileChannel(ciphertextDirFile, EnumSet.of(StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE))).thenReturn(channel);
@@ -1269,9 +1317,11 @@ public void setAttributeOnFile() throws IOException {
CryptoPath path = mock(CryptoPath.class);
Path ciphertextDirPath = mock(Path.class);
Path ciphertextFilePath = mock(Path.class);
+ CiphertextFilePath ciphertextPath = mock(CiphertextFilePath.class);
when(cryptoPathMapper.getCiphertextFileType(path)).thenReturn(CiphertextFileType.FILE);
when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CiphertextDirectory("foo", ciphertextDirPath));
- when(cryptoPathMapper.getCiphertextFilePath(path, CiphertextFileType.FILE)).thenReturn(ciphertextFilePath);
+ when(cryptoPathMapper.getCiphertextFilePath(path)).thenReturn(ciphertextPath);
+ when(ciphertextPath.getFilePath()).thenReturn(ciphertextFilePath);
doThrow(new NoSuchFileException("")).when(provider).checkAccess(ciphertextDirPath);
inTest.setAttribute(path, name, value);
diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java
index a8172d4b..9dd4411b 100644
--- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java
@@ -260,16 +260,53 @@ public void testReadFromSymlink() throws IOException {
@Test
@Order(8)
- @DisplayName("mkdir '/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen'")
- public void testLongFileNames() throws IOException {
- Path longNamePath = fs1.getPath("/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen");
+ @DisplayName("rm /link")
+ public void testRemoveSymlink() throws IOException {
+ Path link = fs1.getPath("/link");
+ Assumptions.assumeTrue(Files.isSymbolicLink(link));
+ Files.delete(link);
+ }
+
+ @Test
+ @Order(9)
+ @DisplayName("mkdir '/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet'")
+ public void testCreateDirWithLongName() throws IOException {
+ Path longNamePath = fs1.getPath("/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet");
Files.createDirectory(longNamePath);
Assertions.assertTrue(Files.isDirectory(longNamePath));
MatcherAssert.assertThat(MoreFiles.listFiles(fs1.getPath("/")), Matchers.hasItem(longNamePath));
}
@Test
- @Order(9)
+ @Order(10)
+ @DisplayName("rm -r '/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet'")
+ public void testRemoveDirWithLongName() throws IOException {
+ Path longNamePath = fs1.getPath("/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet");
+ Files.delete(longNamePath);
+ Assertions.assertTrue(Files.notExists(longNamePath));
+ }
+
+ @Test
+ @Order(11)
+ @DisplayName("touch '/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet'")
+ public void testCreateFileWithLongName() throws IOException {
+ Path longNamePath = fs1.getPath("/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet");
+ Files.createFile(longNamePath);
+ Assertions.assertTrue(Files.isRegularFile(longNamePath));
+ MatcherAssert.assertThat(MoreFiles.listFiles(fs1.getPath("/")), Matchers.hasItem(longNamePath));
+ }
+
+ @Test
+ @Order(12)
+ @DisplayName("rm '/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet'")
+ public void testRemoveFileWithLongName() throws IOException {
+ Path longNamePath = fs1.getPath("/Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet Telefon Energie Wasser Webseitengeraffel Bus Bahn Mietwagen Internet");
+ Files.delete(longNamePath);
+ Assertions.assertTrue(Files.notExists(longNamePath));
+ }
+
+ @Test
+ @Order(13)
@DisplayName("cp fs1:/foo fs2:/bar")
public void testCopyFileAcrossFilesystem() throws IOException {
Path file1 = fs1.getPath("/foo");
@@ -283,7 +320,7 @@ public void testCopyFileAcrossFilesystem() throws IOException {
}
@Test
- @Order(10)
+ @Order(14)
@DisplayName("echo 'goodbye world' > /foo")
public void testWriteToFile() throws IOException {
Path file1 = fs1.getPath("/foo");
@@ -292,7 +329,7 @@ public void testWriteToFile() throws IOException {
}
@Test
- @Order(11)
+ @Order(15)
@DisplayName("cp -f fs1:/foo fs2:/bar")
public void testCopyFileAcrossFilesystemReplaceExisting() throws IOException {
Path file1 = fs1.getPath("/foo");
@@ -306,7 +343,7 @@ public void testCopyFileAcrossFilesystemReplaceExisting() throws IOException {
}
@Test
- @Order(12)
+ @Order(16)
@DisplayName("readattr /attributes.txt")
public void testLazinessOfFileAttributeViews() throws IOException {
Path file = fs1.getPath("/attributes.txt");
@@ -331,7 +368,7 @@ public void testLazinessOfFileAttributeViews() throws IOException {
}
@Test
- @Order(13)
+ @Order(17)
@DisplayName("ln -s /linked/targetY /links/linkX")
public void testSymbolicLinks() throws IOException {
Path linksDir = fs1.getPath("/links");
@@ -370,7 +407,7 @@ public void testSymbolicLinks() throws IOException {
}
@Test
- @Order(13)
+ @Order(18)
@DisplayName("mv -f fs1:/foo fs2:/baz")
public void testMoveFileFromOneCryptoFileSystemToAnother() throws IOException {
Path file1 = fs1.getPath("/foo");
@@ -490,7 +527,7 @@ public void setup(@TempDir Path tmpDir) throws IOException {
Path pathToVault = tmpDir.resolve("vaultDir1");
Files.createDirectories(pathToVault);
CryptoFileSystemProvider.initialize(pathToVault, "masterkey.cryptomator", "asd");
- fs = CryptoFileSystemProvider.newFileSystem(pathToVault, cryptoFileSystemProperties().withPassphrase("asd").build());
+ fs = CryptoFileSystemProvider.newFileSystem(pathToVault, cryptoFileSystemProperties().withPassphrase("asd").build());
}
@Test
diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java
index 0bca259b..4ba4731e 100644
--- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java
@@ -3,6 +3,7 @@
import com.google.common.jimfs.Configuration;
import com.google.common.jimfs.Jimfs;
import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags;
+import org.cryptomator.cryptofs.ch.AsyncDelegatingFileChannel;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.hamcrest.MatcherAssert;
import org.junit.jupiter.api.Assertions;
@@ -361,8 +362,7 @@ public void testNewAsyncFileChannelFailsIfOptionsContainAppend() {
}
@Test
- @SuppressWarnings("deprecation")
- public void testNewAsyncFileChannelReturnsAsyncDelegatingFileChannelWithNewFileChannelAndExecutor() throws IOException {
+ public void testNewAsyncFileChannelReturnsAsyncDelegatingFileChannel() throws IOException {
@SuppressWarnings("unchecked")
Set options = mock(Set.class);
ExecutorService executor = mock(ExecutorService.class);
@@ -370,11 +370,8 @@ public void testNewAsyncFileChannelReturnsAsyncDelegatingFileChannelWithNewFileC
when(cryptoFileSystem.newFileChannel(cryptoPath, options)).thenReturn(channel);
AsynchronousFileChannel result = inTest.newAsynchronousFileChannel(cryptoPath, options, executor);
-
- MatcherAssert.assertThat(result, is(instanceOf(AsyncDelegatingFileChannel.class)));
- AsyncDelegatingFileChannel asyncDelegatingFileChannel = (AsyncDelegatingFileChannel) result;
- Assertions.assertSame(channel, asyncDelegatingFileChannel.getChannel());
- Assertions.assertSame(executor, asyncDelegatingFileChannel.getExecutor());
+
+ MatcherAssert.assertThat(result, instanceOf(AsyncDelegatingFileChannel.class));
}
@Test
diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java
index 829fec4d..fa7cbd71 100644
--- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java
@@ -1,5 +1,6 @@
package org.cryptomator.cryptofs;
+import org.cryptomator.cryptofs.common.DeletingFileVisitor;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java
index 9edb5283..f1771b24 100644
--- a/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java
@@ -8,6 +8,7 @@
*******************************************************************************/
package org.cryptomator.cryptofs;
+import org.cryptomator.cryptofs.common.CiphertextFileType;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.cryptolib.api.FileNameCryptor;
import org.junit.jupiter.api.Assertions;
@@ -19,6 +20,7 @@
import java.io.IOException;
import java.nio.file.FileSystem;
+import java.nio.file.LinkOption;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
@@ -77,11 +79,13 @@ public void testPathEncryptionForFoo() throws IOException {
Mockito.when(fileNameCryptor.hashDirectoryId("")).thenReturn("0000");
Path d0000 = Mockito.mock(Path.class, "d/00/00");
- Path d00000oof = Mockito.mock(Path.class, "d/00/00/0oof");
+ Path d0000oof = Mockito.mock(Path.class, "d/00/00/oof.c9r");
+ Path d0000oofdir = Mockito.mock(Path.class, "d/00/00/oof.c9r/dir.c9r");
Mockito.when(d00.resolve("00")).thenReturn(d0000);
- Mockito.when(d0000.resolve("0oof")).thenReturn(d00000oof);
- Mockito.when(fileNameCryptor.encryptFilename("foo", "".getBytes())).thenReturn("oof");
- Mockito.when(dirIdProvider.load(d00000oof)).thenReturn("1");
+ Mockito.when(d0000.resolve("oof.c9r")).thenReturn(d0000oof);
+ Mockito.when(d0000oof.resolve("dir.c9r")).thenReturn(d0000oofdir);
+ Mockito.when(fileNameCryptor.encryptFilename(Mockito.any(),Mockito.eq("foo"), Mockito.any())).thenReturn("oof");
+ Mockito.when(dirIdProvider.load(d0000oofdir)).thenReturn("1");
Mockito.when(fileNameCryptor.hashDirectoryId("1")).thenReturn("0001");
Path d0001 = Mockito.mock(Path.class);
@@ -99,19 +103,23 @@ public void testPathEncryptionForFooBar() throws IOException {
Mockito.when(fileNameCryptor.hashDirectoryId("")).thenReturn("0000");
Path d0000 = Mockito.mock(Path.class, "d/00/00");
- Path d00000oof = Mockito.mock(Path.class, "d/00/00/0oof");
+ Path d0000oof = Mockito.mock(Path.class, "d/00/00/oof.c9r");
+ Path d0000oofdir = Mockito.mock(Path.class, "d/00/00/oof.c9r/dir.c9r");
Mockito.when(d00.resolve("00")).thenReturn(d0000);
- Mockito.when(d0000.resolve("0oof")).thenReturn(d00000oof);
- Mockito.when(fileNameCryptor.encryptFilename("foo", "".getBytes())).thenReturn("oof");
- Mockito.when(dirIdProvider.load(d00000oof)).thenReturn("1");
+ Mockito.when(d0000.resolve("oof.c9r")).thenReturn(d0000oof);
+ Mockito.when(d0000oof.resolve("dir.c9r")).thenReturn(d0000oofdir);
+ Mockito.when(fileNameCryptor.encryptFilename(Mockito.any(), Mockito.eq("foo"), Mockito.any())).thenReturn("oof");
+ Mockito.when(dirIdProvider.load(d0000oofdir)).thenReturn("1");
Mockito.when(fileNameCryptor.hashDirectoryId("1")).thenReturn("0001");
Path d0001 = Mockito.mock(Path.class, "d/00/01");
- Path d00010rab = Mockito.mock(Path.class, "d/00/01/0rab");
+ Path d0001rab = Mockito.mock(Path.class, "d/00/01/rab.c9r");
+ Path d0000rabdir = Mockito.mock(Path.class, "d/00/00/rab.c9r/dir.c9r");
Mockito.when(d00.resolve("01")).thenReturn(d0001);
- Mockito.when(d0001.resolve("0rab")).thenReturn(d00010rab);
- Mockito.when(fileNameCryptor.encryptFilename("bar", "1".getBytes())).thenReturn("rab");
- Mockito.when(dirIdProvider.load(d00010rab)).thenReturn("2");
+ Mockito.when(d0001.resolve("rab.c9r")).thenReturn(d0001rab);
+ Mockito.when(d0001rab.resolve("dir.c9r")).thenReturn(d0000rabdir);
+ Mockito.when(fileNameCryptor.encryptFilename(Mockito.any(), Mockito.eq("bar"), Mockito.any())).thenReturn("rab");
+ Mockito.when(dirIdProvider.load(d0000rabdir)).thenReturn("2");
Mockito.when(fileNameCryptor.hashDirectoryId("2")).thenReturn("0002");
Path d0002 = Mockito.mock(Path.class);
@@ -129,43 +137,45 @@ public void testPathEncryptionForFooBarBaz() throws IOException {
Mockito.when(fileNameCryptor.hashDirectoryId("")).thenReturn("0000");
Path d0000 = Mockito.mock(Path.class, "d/00/00");
- Path d00000oof = Mockito.mock(Path.class, "d/00/00/0oof");
+ Path d0000oof = Mockito.mock(Path.class, "d/00/00/oof.c9r");
+ Path d0000oofdir = Mockito.mock(Path.class, "d/00/00/oof.c9r/dir.c9r");
Mockito.when(d00.resolve("00")).thenReturn(d0000);
- Mockito.when(d0000.resolve("0oof")).thenReturn(d00000oof);
- Mockito.when(fileNameCryptor.encryptFilename("foo", "".getBytes())).thenReturn("oof");
- Mockito.when(dirIdProvider.load(d00000oof)).thenReturn("1");
+ Mockito.when(d0000.resolve("oof.c9r")).thenReturn(d0000oof);
+ Mockito.when(d0000oof.resolve("dir.c9r")).thenReturn(d0000oofdir);
+ Mockito.when(fileNameCryptor.encryptFilename(Mockito.any(), Mockito.eq("foo"), Mockito.any())).thenReturn("oof");
+ Mockito.when(dirIdProvider.load(d0000oofdir)).thenReturn("1");
Mockito.when(fileNameCryptor.hashDirectoryId("1")).thenReturn("0001");
Path d0001 = Mockito.mock(Path.class, "d/00/01");
- Path d00010rab = Mockito.mock(Path.class, "d/00/01/0rab");
+ Path d0001rab = Mockito.mock(Path.class, "d/00/01/rab.c9r");
+ Path d0000rabdir = Mockito.mock(Path.class, "d/00/00/rab.c9r/dir.c9r");
Mockito.when(d00.resolve("01")).thenReturn(d0001);
- Mockito.when(d0001.resolve("0rab")).thenReturn(d00010rab);
- Mockito.when(fileNameCryptor.encryptFilename("bar", "1".getBytes())).thenReturn("rab");
- Mockito.when(dirIdProvider.load(d00010rab)).thenReturn("2");
+ Mockito.when(d0001.resolve("rab.c9r")).thenReturn(d0001rab);
+ Mockito.when(d0001rab.resolve("dir.c9r")).thenReturn(d0000rabdir);
+ Mockito.when(fileNameCryptor.encryptFilename(Mockito.any(), Mockito.eq("bar"), Mockito.any())).thenReturn("rab");
+ Mockito.when(dirIdProvider.load(d0000rabdir)).thenReturn("2");
Mockito.when(fileNameCryptor.hashDirectoryId("2")).thenReturn("0002");
Path d0002 = Mockito.mock(Path.class, "d/00/02");
- Path d0002zab = Mockito.mock(Path.class, "d/00/02/zab");
- Path d00020zab = Mockito.mock(Path.class, "d/00/02/0zab");
+ Path d0002zab = Mockito.mock(Path.class, "d/00/02/zab.c9r");
Mockito.when(d00.resolve("02")).thenReturn(d0002);
- Mockito.when(d0002.resolve("zab")).thenReturn(d0002zab);
- Mockito.when(d0002.resolve("0zab")).thenReturn(d00020zab);
- Mockito.when(fileNameCryptor.encryptFilename("baz", "2".getBytes())).thenReturn("zab");
+ Mockito.when(d0002.resolve("zab.c9r")).thenReturn(d0002zab);
+ Mockito.when(fileNameCryptor.encryptFilename(Mockito.any(), Mockito.eq("baz"), Mockito.any())).thenReturn("zab");
CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider);
- Path path = mapper.getCiphertextFilePath(fileSystem.getPath("/foo/bar/baz"), CiphertextFileType.FILE);
+ Path path = mapper.getCiphertextFilePath(fileSystem.getPath("/foo/bar/baz")).getRawPath();
Assertions.assertEquals(d0002zab, path);
- Path path2 = mapper.getCiphertextFilePath(fileSystem.getPath("/foo/bar/baz"), CiphertextFileType.DIRECTORY);
- Assertions.assertEquals(d00020zab, path2);
}
@Nested
public class GetCiphertextFileType {
private FileSystemProvider underlyingFileSystemProvider;
- private Path d0000CIPHER;
- private Path d00000CIPHER;
- private Path d00001SCIPHER;
+ private Path c9rPath;
+ private Path dirFilePath;
+ private Path symlinkFilePath;
+ private Path contentsFilePath;
+ private BasicFileAttributes c9rAttrs;
@BeforeEach
public void setup() throws IOException {
@@ -179,16 +189,22 @@ public void setup() throws IOException {
Mockito.when(d00.resolve("00")).thenReturn(d0000);
Mockito.when(fileNameCryptor.hashDirectoryId("")).thenReturn("0000");
- Mockito.when(fileNameCryptor.encryptFilename("CLEAR", "".getBytes())).thenReturn("CIPHER");
- d0000CIPHER = Mockito.mock(Path.class, "d/00/00/CIPHER");
- d00000CIPHER = Mockito.mock(Path.class, "d/00/00/0CIPHER");
- d00001SCIPHER = Mockito.mock(Path.class, "d/00/00/1SCIPHER");
- Mockito.when(d0000.resolve("CIPHER")).thenReturn(d0000CIPHER);
- Mockito.when(d0000.resolve("0CIPHER")).thenReturn(d00000CIPHER);
- Mockito.when(d0000.resolve("1SCIPHER")).thenReturn(d00001SCIPHER);
- Mockito.when(d0000CIPHER.getFileSystem()).thenReturn(underlyingFileSystem);
- Mockito.when(d00000CIPHER.getFileSystem()).thenReturn(underlyingFileSystem);
- Mockito.when(d00001SCIPHER.getFileSystem()).thenReturn(underlyingFileSystem);
+ Mockito.when(fileNameCryptor.encryptFilename(Mockito.any(), Mockito.eq("CLEAR"), Mockito.any())).thenReturn("CIPHER");
+ c9rPath = Mockito.mock(Path.class, "d/00/00/CIPHER.c9r");
+ c9rAttrs = Mockito.mock(BasicFileAttributes.class, "attributes for d/00/00/CIPHER.c9r");
+ Mockito.when(d0000.resolve("CIPHER.c9r")).thenReturn(c9rPath);
+ Mockito.when(c9rPath.getFileSystem()).thenReturn(underlyingFileSystem);
+
+ dirFilePath = Mockito.mock(Path.class, "d/00/00/CIPHER.c9r/dir.c9r");
+ symlinkFilePath = Mockito.mock(Path.class, "d/00/00/CIPHER.c9r/symlink.c9r");
+ contentsFilePath = Mockito.mock(Path.class, "d/00/00/CIPHER.c9r/contents.c9r");
+ Mockito.when(c9rPath.resolve("dir.c9r")).thenReturn(dirFilePath);
+ Mockito.when(c9rPath.resolve("symlink.c9r")).thenReturn(symlinkFilePath);
+ Mockito.when(c9rPath.resolve("contents.c9r")).thenReturn(contentsFilePath);
+ Mockito.when(dirFilePath.getFileSystem()).thenReturn(underlyingFileSystem);
+ Mockito.when(symlinkFilePath.getFileSystem()).thenReturn(underlyingFileSystem);
+ Mockito.when(contentsFilePath.getFileSystem()).thenReturn(underlyingFileSystem);
+
}
@@ -201,9 +217,7 @@ public void testGetCiphertextFileTypeOfRootPath() throws IOException {
@Test
public void testGetCiphertextFileTypeForNonexistingFile() throws IOException {
- Mockito.when(underlyingFileSystemProvider.readAttributes(d0000CIPHER, BasicFileAttributes.class)).thenThrow(NoSuchFileException.class);
- Mockito.when(underlyingFileSystemProvider.readAttributes(d00000CIPHER, BasicFileAttributes.class)).thenThrow(NoSuchFileException.class);
- Mockito.when(underlyingFileSystemProvider.readAttributes(d00001SCIPHER, BasicFileAttributes.class)).thenThrow(NoSuchFileException.class);
+ Mockito.when(underlyingFileSystemProvider.readAttributes(c9rPath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenThrow(NoSuchFileException.class);
CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider);
@@ -215,9 +229,8 @@ public void testGetCiphertextFileTypeForNonexistingFile() throws IOException {
@Test
public void testGetCiphertextFileTypeForFile() throws IOException {
- Mockito.when(underlyingFileSystemProvider.readAttributes(d0000CIPHER, BasicFileAttributes.class)).thenReturn(Mockito.mock(BasicFileAttributes.class));
- Mockito.when(underlyingFileSystemProvider.readAttributes(d00000CIPHER, BasicFileAttributes.class)).thenThrow(NoSuchFileException.class);
- Mockito.when(underlyingFileSystemProvider.readAttributes(d00001SCIPHER, BasicFileAttributes.class)).thenThrow(NoSuchFileException.class);
+ Mockito.when(underlyingFileSystemProvider.readAttributes(c9rPath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenReturn(c9rAttrs);
+ Mockito.when(c9rAttrs.isRegularFile()).thenReturn(true);
CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider);
@@ -228,9 +241,11 @@ public void testGetCiphertextFileTypeForFile() throws IOException {
@Test
public void testGetCiphertextFileTypeForDirectory() throws IOException {
- Mockito.when(underlyingFileSystemProvider.readAttributes(d0000CIPHER, BasicFileAttributes.class)).thenThrow(NoSuchFileException.class);
- Mockito.when(underlyingFileSystemProvider.readAttributes(d00000CIPHER, BasicFileAttributes.class)).thenReturn(Mockito.mock(BasicFileAttributes.class));
- Mockito.when(underlyingFileSystemProvider.readAttributes(d00001SCIPHER, BasicFileAttributes.class)).thenThrow(NoSuchFileException.class);
+ Mockito.when(underlyingFileSystemProvider.readAttributes(c9rPath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenReturn(c9rAttrs);
+ Mockito.when(c9rAttrs.isDirectory()).thenReturn(true);
+ Mockito.when(underlyingFileSystemProvider.readAttributes(dirFilePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenReturn(Mockito.mock(BasicFileAttributes.class));
+ Mockito.when(underlyingFileSystemProvider.readAttributes(symlinkFilePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenThrow(NoSuchFileException.class);
+ Mockito.when(underlyingFileSystemProvider.readAttributes(contentsFilePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenThrow(NoSuchFileException.class);
CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider);
@@ -241,9 +256,11 @@ public void testGetCiphertextFileTypeForDirectory() throws IOException {
@Test
public void testGetCiphertextFileTypeForSymlink() throws IOException {
- Mockito.when(underlyingFileSystemProvider.readAttributes(d0000CIPHER, BasicFileAttributes.class)).thenThrow(NoSuchFileException.class);
- Mockito.when(underlyingFileSystemProvider.readAttributes(d00000CIPHER, BasicFileAttributes.class)).thenThrow(NoSuchFileException.class);
- Mockito.when(underlyingFileSystemProvider.readAttributes(d00001SCIPHER, BasicFileAttributes.class)).thenReturn(Mockito.mock(BasicFileAttributes.class));
+ Mockito.when(underlyingFileSystemProvider.readAttributes(c9rPath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenReturn(c9rAttrs);
+ Mockito.when(c9rAttrs.isDirectory()).thenReturn(true);
+ Mockito.when(underlyingFileSystemProvider.readAttributes(dirFilePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenThrow(NoSuchFileException.class);
+ Mockito.when(underlyingFileSystemProvider.readAttributes(symlinkFilePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenReturn(Mockito.mock(BasicFileAttributes.class));
+ Mockito.when(underlyingFileSystemProvider.readAttributes(contentsFilePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenThrow(NoSuchFileException.class);
CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider);
diff --git a/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java
index dadc6853..d8d47c54 100644
--- a/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java
@@ -8,8 +8,11 @@
*******************************************************************************/
package org.cryptomator.cryptofs;
+import com.google.common.base.Strings;
+import org.cryptomator.cryptofs.common.Constants;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
@@ -25,7 +28,7 @@
import java.util.stream.Stream;
import static java.nio.file.StandardOpenOption.CREATE_NEW;
-import static org.cryptomator.cryptofs.Constants.SHORT_NAMES_MAX_LENGTH;
+import static org.cryptomator.cryptofs.common.Constants.MAX_CIPHERTEXT_NAME_LENGTH;
import static org.cryptomator.cryptofs.CryptoFileSystemProperties.cryptoFileSystemProperties;
import static org.cryptomator.cryptofs.CryptoFileSystemUri.create;
@@ -35,13 +38,11 @@
public class DeleteNonEmptyCiphertextDirectoryIntegrationTest {
private static Path pathToVault;
- private static Path mDir;
private static FileSystem fileSystem;
@BeforeAll
public static void setupClass(@TempDir Path tmpDir) throws IOException {
pathToVault = tmpDir.resolve("vault");
- mDir = pathToVault.resolve("m");
Files.createDirectory(pathToVault);
fileSystem = new CryptoFileSystemProvider().newFileSystem(create(pathToVault), cryptoFileSystemProperties().withPassphrase("asd").build());
}
@@ -79,26 +80,28 @@ public void testDeleteCiphertextDirectoryContainingDirectories() throws IOExcept
}
@Test
+ @Disabled // c9s not yet implemented
public void testDeleteDirectoryContainingLongNameFileWithoutMetadata() throws IOException {
Path cleartextDirectory = fileSystem.getPath("/b");
Files.createDirectory(cleartextDirectory);
Path ciphertextDirectory = firstEmptyCiphertextDirectory();
- createFile(ciphertextDirectory, "HHEZJURE.lng", new byte[] {65});
+ Path longNameDir = createFolder(ciphertextDirectory, "HHEZJURE.c9s");
+ createFile(longNameDir, Constants.CONTENTS_FILE_NAME, new byte[] {65});
Files.delete(cleartextDirectory);
}
@Test
+ @Disabled // c9s not yet implemented
public void testDeleteDirectoryContainingUnauthenticLongNameDirectoryFile() throws IOException {
Path cleartextDirectory = fileSystem.getPath("/c");
Files.createDirectory(cleartextDirectory);
Path ciphertextDirectory = firstEmptyCiphertextDirectory();
- createFile(ciphertextDirectory, "HHEZJURE.lng", new byte[] {65});
- Path mSubdir = mDir.resolve("HH").resolve("EZ");
- Files.createDirectories(mSubdir);
- createFile(mSubdir, "HHEZJURE.lng", "0HHEZJUREHHEZJUREHHEZJURE".getBytes());
+ Path longNameDir = createFolder(ciphertextDirectory, "HHEZJURE.c9s");
+ createFile(longNameDir, Constants.INFLATED_FILE_NAME, "HHEZJUREHHEZJUREHHEZJURE".getBytes());
+ createFile(longNameDir, Constants.CONTENTS_FILE_NAME, new byte[] {65});
Files.delete(cleartextDirectory);
}
@@ -115,15 +118,14 @@ public void testDeleteNonEmptyDir() throws IOException {
}
@Test
+ @Disabled // c9s not yet implemented
public void testDeleteDirectoryContainingLongNamedDirectory() throws IOException {
Path cleartextDirectory = fileSystem.getPath("/e");
Files.createDirectory(cleartextDirectory);
// a
// .. LongNameaaa...
- String name = "LongName" + IntStream.range(0, SHORT_NAMES_MAX_LENGTH) //
- .mapToObj(ignored -> "a") //
- .collect(Collectors.joining());
+ String name = "LongName" + Strings.repeat("a", MAX_CIPHERTEXT_NAME_LENGTH);
createFolder(cleartextDirectory, name);
Assertions.assertThrows(DirectoryNotEmptyException.class, () -> {
diff --git a/src/test/java/org/cryptomator/cryptofs/EncryptedNamePatternTest.java b/src/test/java/org/cryptomator/cryptofs/EncryptedNamePatternTest.java
deleted file mode 100644
index f744bedb..00000000
--- a/src/test/java/org/cryptomator/cryptofs/EncryptedNamePatternTest.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package org.cryptomator.cryptofs;
-
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Test;
-
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Optional;
-
-public class EncryptedNamePatternTest {
-
- private static final String ENCRYPTED_NAME = "ALKDUEEH2445375AUZEJFEFA";
- private static final Path PATH_WITHOUT_ENCRYPTED_NAME = Paths.get("foo.txt");
- private static final Path PATH_WITH_ENCRYPTED_NAME_AND_PREFIX_AND_SUFFIX = Paths.get("foo" + ENCRYPTED_NAME + ".txt");
- private static final Path PATH_WITH_ENCRYPTED_NAME_AND_SUFFIX = Paths.get(ENCRYPTED_NAME + ".txt");
-
- private EncryptedNamePattern inTest = new EncryptedNamePattern();
-
- @Test
- public void testExtractEncryptedNameReturnsEmptyOptionalIfNoEncryptedNameIsPresent() {
- Optional result = inTest.extractEncryptedName(PATH_WITHOUT_ENCRYPTED_NAME);
-
- Assertions.assertFalse(result.isPresent());
- }
-
- @Test
- public void testExtractEncryptedNameReturnsEncryptedNameIfItIsIsPresent() {
- Optional result = inTest.extractEncryptedName(PATH_WITH_ENCRYPTED_NAME_AND_PREFIX_AND_SUFFIX);
-
- Assertions.assertTrue(result.isPresent());
- Assertions.assertEquals(ENCRYPTED_NAME, result.get());
- }
-
- @Test
- public void testExtractEncryptedNameFromStartReturnsEmptyOptionalIfNoEncryptedNameIsPresent() {
- Optional result = inTest.extractEncryptedNameFromStart(PATH_WITHOUT_ENCRYPTED_NAME);
-
- Assertions.assertFalse(result.isPresent());
- }
-
- @Test
- public void testExtractEncryptedNameFromStartReturnsEncryptedNameIfItIsPresent() {
- Optional result = inTest.extractEncryptedName(PATH_WITH_ENCRYPTED_NAME_AND_SUFFIX);
-
- Assertions.assertTrue(result.isPresent());
- Assertions.assertEquals(ENCRYPTED_NAME, result.get());
- }
-
- @Test
- public void testExtractEncryptedNameFromStartReturnsEmptyOptionalIfEncryptedNameIsPresentAfterStart() {
- Optional result = inTest.extractEncryptedNameFromStart(PATH_WITH_ENCRYPTED_NAME_AND_PREFIX_AND_SUFFIX);
-
- Assertions.assertFalse(result.isPresent());
- }
-
-}
diff --git a/src/test/java/org/cryptomator/cryptofs/LongFileNameProviderTest.java b/src/test/java/org/cryptomator/cryptofs/LongFileNameProviderTest.java
index 344d0f04..853d2440 100644
--- a/src/test/java/org/cryptomator/cryptofs/LongFileNameProviderTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/LongFileNameProviderTest.java
@@ -45,60 +45,58 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
}
@Test
- public void testIsDeflated(@TempDir Path tmpPath) {
- Path aPath = tmpPath.resolve("foo");
- Assertions.assertTrue(new LongFileNameProvider(aPath, readonlyFlag).isDeflated("foo.lng"));
- Assertions.assertFalse(new LongFileNameProvider(aPath, readonlyFlag).isDeflated("foo.txt"));
+ public void testIsDeflated() {
+ Assertions.assertTrue(new LongFileNameProvider(readonlyFlag).isDeflated("foo.c9s"));
+ Assertions.assertFalse(new LongFileNameProvider(readonlyFlag).isDeflated("foo.txt"));
}
@Test
public void testDeflateAndInflate(@TempDir Path tmpPath) throws IOException {
String orig = "longName";
- LongFileNameProvider prov1 = new LongFileNameProvider(tmpPath, readonlyFlag);
- String deflated = prov1.deflate(orig);
- String inflated1 = prov1.inflate(deflated);
+ LongFileNameProvider prov1 = new LongFileNameProvider(readonlyFlag);
+ LongFileNameProvider.DeflatedFileName deflated = prov1.deflate(tmpPath.resolve(orig));
+ String inflated1 = prov1.inflate(deflated.c9sPath);
Assertions.assertEquals(orig, inflated1);
Assertions.assertEquals(0, countFiles(tmpPath));
- prov1.getCached(Paths.get(deflated)).ifPresent(LongFileNameProvider.DeflatedFileName::persist);
+ deflated.persist();
Assertions.assertEquals(1, countFiles(tmpPath));
- LongFileNameProvider prov2 = new LongFileNameProvider(tmpPath, readonlyFlag);
- String inflated2 = prov2.inflate(deflated);
+ LongFileNameProvider prov2 = new LongFileNameProvider(readonlyFlag);
+ String inflated2 = prov2.inflate(deflated.c9sPath);
Assertions.assertEquals(orig, inflated2);
}
@Test
- public void testInflateNonExisting(@TempDir Path tmpPath) {
- LongFileNameProvider prov = new LongFileNameProvider(tmpPath, readonlyFlag);
+ public void testInflateNonExisting() {
+ LongFileNameProvider prov = new LongFileNameProvider(readonlyFlag);
Assertions.assertThrows(NoSuchFileException.class, () -> {
- prov.inflate("doesNotExist");
+ prov.inflate(Paths.get("/does/not/exist"));
});
}
@Test
public void testDeflateMultipleTimes(@TempDir Path tmpPath) {
- LongFileNameProvider prov = new LongFileNameProvider(tmpPath, readonlyFlag);
- String orig = "longName";
- prov.deflate(orig);
- prov.deflate(orig);
- prov.deflate(orig);
- prov.deflate(orig);
+ LongFileNameProvider prov = new LongFileNameProvider(readonlyFlag);
+ Path canonicalFileName = tmpPath.resolve("longName");
+ prov.deflate(canonicalFileName);
+ prov.deflate(canonicalFileName);
+ prov.deflate(canonicalFileName);
+ prov.deflate(canonicalFileName);
}
@Test
public void testPerstistCachedFailsOnReadOnlyFileSystems(@TempDir Path tmpPath) {
- LongFileNameProvider prov = new LongFileNameProvider(tmpPath, readonlyFlag);
+ LongFileNameProvider prov = new LongFileNameProvider(readonlyFlag);
String orig = "longName";
- String shortened = prov.deflate(orig);
- Optional cachedFileName = prov.getCached(Paths.get(shortened));
+ Path canonicalFileName = tmpPath.resolve(orig);
+ LongFileNameProvider.DeflatedFileName deflated = prov.deflate(canonicalFileName);
- Assertions.assertTrue(cachedFileName.isPresent());
Mockito.doThrow(new ReadOnlyFileSystemException()).when(readonlyFlag).assertWritable();
Assertions.assertThrows(ReadOnlyFileSystemException.class, () -> {
- cachedFileName.get().persist();
+ deflated.persist();
});
}
diff --git a/src/test/java/org/cryptomator/cryptofs/SymlinksTest.java b/src/test/java/org/cryptomator/cryptofs/SymlinksTest.java
index 89e3d0d8..0fa01436 100644
--- a/src/test/java/org/cryptomator/cryptofs/SymlinksTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/SymlinksTest.java
@@ -1,6 +1,6 @@
package org.cryptomator.cryptofs;
-import org.cryptomator.cryptofs.fh.OpenCryptoFile;
+import org.cryptomator.cryptofs.common.CiphertextFileType;
import org.cryptomator.cryptofs.fh.OpenCryptoFiles;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
@@ -11,9 +11,13 @@
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
+import java.nio.file.FileSystem;
import java.nio.file.FileSystemLoopException;
+import java.nio.file.LinkOption;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.spi.FileSystemProvider;
public class SymlinksTest {
@@ -21,11 +25,8 @@ public class SymlinksTest {
private final LongFileNameProvider longFileNameProvider = Mockito.mock(LongFileNameProvider.class);
private final OpenCryptoFiles openCryptoFiles = Mockito.mock(OpenCryptoFiles.class);
private final ReadonlyFlag readonlyFlag = Mockito.mock(ReadonlyFlag.class);
-
- private final CryptoFileSystemImpl fs = Mockito.mock(CryptoFileSystemImpl.class, "cryptoFs");
- private final CryptoPath cleartextPath = Mockito.mock(CryptoPath.class, "cleartextPath");
- private final OpenCryptoFile ciphertextFile = Mockito.mock(OpenCryptoFile.class);
- private final Path ciphertextPath = Mockito.mock(Path.class, "ciphertextPath");
+ private final FileSystem underlyingFs = Mockito.mock(FileSystem.class);
+ private final FileSystemProvider underlyingFsProvider = Mockito.mock(FileSystemProvider.class);
private Symlinks inTest;
@@ -33,32 +34,59 @@ public class SymlinksTest {
public void setup() throws IOException {
inTest = new Symlinks(cryptoPathMapper, longFileNameProvider, openCryptoFiles, readonlyFlag);
- Mockito.when(cleartextPath.getFileSystem()).thenReturn(fs);
- Mockito.when(openCryptoFiles.getOrCreate(ciphertextPath)).thenReturn(ciphertextFile);
+ Mockito.when(underlyingFs.provider()).thenReturn(underlyingFsProvider);
+ }
+
+ private Path mockExistingSymlink(CryptoPath cleartextPath) throws IOException {
+ Path ciphertextRawPath = Mockito.mock(Path.class);
+ Path symlinkFilePath = Mockito.mock(Path.class);
+ BasicFileAttributes ciphertextPathAttr = Mockito.mock(BasicFileAttributes.class);
+ BasicFileAttributes symlinkFilePathAttr = Mockito.mock(BasicFileAttributes.class);
+ CiphertextFilePath ciphertextPath = Mockito.mock(CiphertextFilePath.class);
+ Mockito.when(ciphertextRawPath.resolve("symlink.c9r")).thenReturn(symlinkFilePath);
+ Mockito.when(symlinkFilePath.getParent()).thenReturn(ciphertextRawPath);
+ Mockito.when(ciphertextRawPath.getFileSystem()).thenReturn(underlyingFs);
+ Mockito.when(symlinkFilePath.getFileSystem()).thenReturn(underlyingFs);
+ Mockito.when(underlyingFsProvider.readAttributes(ciphertextRawPath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenReturn(ciphertextPathAttr);
+ Mockito.when(underlyingFsProvider.readAttributes(symlinkFilePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenReturn(symlinkFilePathAttr);
+ Mockito.when(ciphertextPathAttr.isDirectory()).thenReturn(true);
+ Mockito.when(symlinkFilePathAttr.isRegularFile()).thenReturn(true);
+ Mockito.when(cryptoPathMapper.getCiphertextFilePath(cleartextPath)).thenReturn(ciphertextPath);
+ Mockito.when(ciphertextPath.getRawPath()).thenReturn(ciphertextRawPath);
+ Mockito.when(ciphertextPath.getSymlinkFilePath()).thenReturn(symlinkFilePath);
+ return ciphertextRawPath;
}
@Test
public void testCreateSymbolicLink() throws IOException {
+ CryptoPath cleartextPath = Mockito.mock(CryptoPath.class);
Path target = Mockito.mock(Path.class, "targetPath");
+ Path ciphertextPath = mockExistingSymlink(cleartextPath);
+ Path symlinkFilePath = ciphertextPath.resolve("symlink.c9r");
Mockito.doNothing().when(cryptoPathMapper).assertNonExisting(cleartextPath);
- Mockito.when(cryptoPathMapper.getCiphertextFilePath(cleartextPath, CiphertextFileType.SYMLINK)).thenReturn(ciphertextPath);
Mockito.when(target.toString()).thenReturn("/symlink/target/path");
inTest.createSymbolicLink(cleartextPath, target);
ArgumentCaptor bytesWritten = ArgumentCaptor.forClass(ByteBuffer.class);
- Mockito.verify(openCryptoFiles).writeCiphertextFile(Mockito.eq(ciphertextPath), Mockito.any(), bytesWritten.capture());
+ Mockito.verify(underlyingFsProvider).createDirectory(Mockito.eq(ciphertextPath), Mockito.any());
+ Mockito.verify(openCryptoFiles).writeCiphertextFile(Mockito.eq(symlinkFilePath), Mockito.any(), bytesWritten.capture());
Assertions.assertEquals("/symlink/target/path", StandardCharsets.UTF_8.decode(bytesWritten.getValue()).toString());
}
@Test
public void testReadSymbolicLink() throws IOException {
+ CryptoPath cleartextPath = Mockito.mock(CryptoPath.class);
+ CryptoFileSystemImpl cleartextFs = Mockito.mock(CryptoFileSystemImpl.class);
+ Mockito.when(cleartextPath.getFileSystem()).thenReturn(cleartextFs);
+
String targetPath = "/symlink/target/path2";
CryptoPath resolvedTargetPath = Mockito.mock(CryptoPath.class, "resolvedTargetPath");
- Mockito.when(cryptoPathMapper.getCiphertextFilePath(cleartextPath, CiphertextFileType.SYMLINK)).thenReturn(ciphertextPath);
- Mockito.when(openCryptoFiles.readCiphertextFile(Mockito.eq(ciphertextPath), Mockito.any(), Mockito.anyInt())).thenReturn(StandardCharsets.UTF_8.encode(targetPath));
+ Path ciphertextPath = mockExistingSymlink(cleartextPath);
+ Path symlinkFilePath = ciphertextPath.resolve("symlink.c9r");
+ Mockito.when(openCryptoFiles.readCiphertextFile(Mockito.eq(symlinkFilePath), Mockito.any(), Mockito.anyInt())).thenReturn(StandardCharsets.UTF_8.encode(targetPath));
- Mockito.when(fs.getPath(targetPath)).thenReturn(resolvedTargetPath);
+ Mockito.when(cleartextFs.getPath(targetPath)).thenReturn(resolvedTargetPath);
CryptoPath read = inTest.readSymbolicLink(cleartextPath);
@@ -67,33 +95,34 @@ public void testReadSymbolicLink() throws IOException {
@Test
public void testResolveRecursivelyForRegularFile() throws IOException {
- CryptoPath cleartextPath1 = Mockito.mock(CryptoPath.class);
- Mockito.when(cryptoPathMapper.getCiphertextFileType(cleartextPath1)).thenReturn(CiphertextFileType.FILE);
+ CryptoPath cleartextPath = Mockito.mock(CryptoPath.class);
+ Mockito.when(cryptoPathMapper.getCiphertextFileType(cleartextPath)).thenReturn(CiphertextFileType.FILE);
- CryptoPath resolved = inTest.resolveRecursively(cleartextPath1);
+ CryptoPath resolved = inTest.resolveRecursively(cleartextPath);
- Assertions.assertSame(cleartextPath1, resolved);
+ Assertions.assertSame(cleartextPath, resolved);
}
@Test
public void testResolveRecursively() throws IOException {
+ CryptoFileSystemImpl cleartextFs = Mockito.mock(CryptoFileSystemImpl.class);
CryptoPath cleartextPath1 = Mockito.mock(CryptoPath.class);
CryptoPath cleartextPath2 = Mockito.mock(CryptoPath.class);
CryptoPath cleartextPath3 = Mockito.mock(CryptoPath.class);
- Path ciphertextPath1 = Mockito.mock(Path.class);
- Path ciphertextPath2 = Mockito.mock(Path.class);
- Mockito.when(cleartextPath1.getFileSystem()).thenReturn(fs);
- Mockito.when(cleartextPath2.getFileSystem()).thenReturn(fs);
- Mockito.when(cleartextPath3.getFileSystem()).thenReturn(fs);
+ Path ciphertextPath1 = mockExistingSymlink(cleartextPath1);
+ Path ciphertextPath2 = mockExistingSymlink(cleartextPath2);
+ Path ciphertextSymlinkPath1 = ciphertextPath1.resolve("symlink.c9r");
+ Path ciphertextSymlinkPath2 = ciphertextPath2.resolve("symlink.c9r");
+ Mockito.when(cleartextPath1.getFileSystem()).thenReturn(cleartextFs);
+ Mockito.when(cleartextPath2.getFileSystem()).thenReturn(cleartextFs);
+ Mockito.when(cleartextPath3.getFileSystem()).thenReturn(cleartextFs);
Mockito.when(cryptoPathMapper.getCiphertextFileType(cleartextPath1)).thenReturn(CiphertextFileType.SYMLINK);
Mockito.when(cryptoPathMapper.getCiphertextFileType(cleartextPath2)).thenReturn(CiphertextFileType.SYMLINK);
Mockito.when(cryptoPathMapper.getCiphertextFileType(cleartextPath3)).thenReturn(CiphertextFileType.FILE);
- Mockito.when(cryptoPathMapper.getCiphertextFilePath(cleartextPath1, CiphertextFileType.SYMLINK)).thenReturn(ciphertextPath1);
- Mockito.when(cryptoPathMapper.getCiphertextFilePath(cleartextPath2, CiphertextFileType.SYMLINK)).thenReturn(ciphertextPath2);
- Mockito.when(openCryptoFiles.readCiphertextFile(Mockito.eq(ciphertextPath1), Mockito.any(), Mockito.anyInt())).thenReturn(StandardCharsets.UTF_8.encode("file2"));
- Mockito.when(openCryptoFiles.readCiphertextFile(Mockito.eq(ciphertextPath2), Mockito.any(), Mockito.anyInt())).thenReturn(StandardCharsets.UTF_8.encode("file3"));
- Mockito.when(fs.getPath("file2")).thenReturn(cleartextPath2);
- Mockito.when(fs.getPath("file3")).thenReturn(cleartextPath3);
+ Mockito.when(openCryptoFiles.readCiphertextFile(Mockito.eq(ciphertextSymlinkPath1), Mockito.any(), Mockito.anyInt())).thenReturn(StandardCharsets.UTF_8.encode("file2"));
+ Mockito.when(openCryptoFiles.readCiphertextFile(Mockito.eq(ciphertextSymlinkPath2), Mockito.any(), Mockito.anyInt())).thenReturn(StandardCharsets.UTF_8.encode("file3"));
+ Mockito.when(cleartextFs.getPath("file2")).thenReturn(cleartextPath2);
+ Mockito.when(cleartextFs.getPath("file3")).thenReturn(cleartextPath3);
CryptoPath resolved = inTest.resolveRecursively(cleartextPath1);
@@ -102,16 +131,17 @@ public void testResolveRecursively() throws IOException {
@Test
public void testResolveRecursivelyWithNonExistingTarget() throws IOException {
+ CryptoFileSystemImpl cleartextFs = Mockito.mock(CryptoFileSystemImpl.class);
CryptoPath cleartextPath1 = Mockito.mock(CryptoPath.class);
CryptoPath cleartextPath2 = Mockito.mock(CryptoPath.class);
- Path ciphertextPath1 = Mockito.mock(Path.class);
- Mockito.when(cleartextPath1.getFileSystem()).thenReturn(fs);
- Mockito.when(cleartextPath2.getFileSystem()).thenReturn(fs);
+ Path ciphertextPath1 = mockExistingSymlink(cleartextPath1);
+ Path ciphertextSymlinkPath1 = ciphertextPath1.resolve("symlink.c9r");
+ Mockito.when(cleartextPath1.getFileSystem()).thenReturn(cleartextFs);
+ Mockito.when(cleartextPath2.getFileSystem()).thenReturn(cleartextFs);
Mockito.when(cryptoPathMapper.getCiphertextFileType(cleartextPath1)).thenReturn(CiphertextFileType.SYMLINK);
Mockito.when(cryptoPathMapper.getCiphertextFileType(cleartextPath2)).thenThrow(new NoSuchFileException("cleartextPath2"));
- Mockito.when(cryptoPathMapper.getCiphertextFilePath(cleartextPath1, CiphertextFileType.SYMLINK)).thenReturn(ciphertextPath1);
- Mockito.when(openCryptoFiles.readCiphertextFile(Mockito.eq(ciphertextPath1), Mockito.any(), Mockito.anyInt())).thenReturn(StandardCharsets.UTF_8.encode("file2"));
- Mockito.when(fs.getPath("file2")).thenReturn(cleartextPath2);
+ Mockito.when(openCryptoFiles.readCiphertextFile(Mockito.eq(ciphertextSymlinkPath1), Mockito.any(), Mockito.anyInt())).thenReturn(StandardCharsets.UTF_8.encode("file2"));
+ Mockito.when(cleartextFs.getPath("file2")).thenReturn(cleartextPath2);
CryptoPath resolved = inTest.resolveRecursively(cleartextPath1);
@@ -120,27 +150,28 @@ public void testResolveRecursivelyWithNonExistingTarget() throws IOException {
@Test
public void testResolveRecursivelyWithLoop() throws IOException {
+ CryptoFileSystemImpl cleartextFs = Mockito.mock(CryptoFileSystemImpl.class);
CryptoPath cleartextPath1 = Mockito.mock(CryptoPath.class);
CryptoPath cleartextPath2 = Mockito.mock(CryptoPath.class);
CryptoPath cleartextPath3 = Mockito.mock(CryptoPath.class);
- Path ciphertextPath1 = Mockito.mock(Path.class);
- Path ciphertextPath2 = Mockito.mock(Path.class);
- Path ciphertextPath3 = Mockito.mock(Path.class);
- Mockito.when(cleartextPath1.getFileSystem()).thenReturn(fs);
- Mockito.when(cleartextPath2.getFileSystem()).thenReturn(fs);
- Mockito.when(cleartextPath3.getFileSystem()).thenReturn(fs);
+ Path ciphertextPath1 = mockExistingSymlink(cleartextPath1);
+ Path ciphertextPath2 = mockExistingSymlink(cleartextPath2);
+ Path ciphertextPath3 = mockExistingSymlink(cleartextPath3);
+ Path ciphertextSymlinkPath1 = ciphertextPath1.resolve("symlink.c9r");
+ Path ciphertextSymlinkPath2 = ciphertextPath2.resolve("symlink.c9r");
+ Path ciphertextSymlinkPath3 = ciphertextPath3.resolve("symlink.c9r");
+ Mockito.when(cleartextPath1.getFileSystem()).thenReturn(cleartextFs);
+ Mockito.when(cleartextPath2.getFileSystem()).thenReturn(cleartextFs);
+ Mockito.when(cleartextPath3.getFileSystem()).thenReturn(cleartextFs);
Mockito.when(cryptoPathMapper.getCiphertextFileType(cleartextPath1)).thenReturn(CiphertextFileType.SYMLINK);
Mockito.when(cryptoPathMapper.getCiphertextFileType(cleartextPath2)).thenReturn(CiphertextFileType.SYMLINK);
Mockito.when(cryptoPathMapper.getCiphertextFileType(cleartextPath3)).thenReturn(CiphertextFileType.SYMLINK);
- Mockito.when(cryptoPathMapper.getCiphertextFilePath(cleartextPath1, CiphertextFileType.SYMLINK)).thenReturn(ciphertextPath1);
- Mockito.when(cryptoPathMapper.getCiphertextFilePath(cleartextPath2, CiphertextFileType.SYMLINK)).thenReturn(ciphertextPath2);
- Mockito.when(cryptoPathMapper.getCiphertextFilePath(cleartextPath3, CiphertextFileType.SYMLINK)).thenReturn(ciphertextPath3);
- Mockito.when(openCryptoFiles.readCiphertextFile(Mockito.eq(ciphertextPath1), Mockito.any(), Mockito.anyInt())).thenReturn(StandardCharsets.UTF_8.encode("file2"));
- Mockito.when(openCryptoFiles.readCiphertextFile(Mockito.eq(ciphertextPath2), Mockito.any(), Mockito.anyInt())).thenReturn(StandardCharsets.UTF_8.encode("file3"));
- Mockito.when(openCryptoFiles.readCiphertextFile(Mockito.eq(ciphertextPath3), Mockito.any(), Mockito.anyInt())).thenReturn(StandardCharsets.UTF_8.encode("file1"));
- Mockito.when(fs.getPath("file2")).thenReturn(cleartextPath2);
- Mockito.when(fs.getPath("file3")).thenReturn(cleartextPath3);
- Mockito.when(fs.getPath("file1")).thenReturn(cleartextPath1);
+ Mockito.when(openCryptoFiles.readCiphertextFile(Mockito.eq(ciphertextSymlinkPath1), Mockito.any(), Mockito.anyInt())).thenReturn(StandardCharsets.UTF_8.encode("file2"));
+ Mockito.when(openCryptoFiles.readCiphertextFile(Mockito.eq(ciphertextSymlinkPath2), Mockito.any(), Mockito.anyInt())).thenReturn(StandardCharsets.UTF_8.encode("file3"));
+ Mockito.when(openCryptoFiles.readCiphertextFile(Mockito.eq(ciphertextSymlinkPath3), Mockito.any(), Mockito.anyInt())).thenReturn(StandardCharsets.UTF_8.encode("file1"));
+ Mockito.when(cleartextFs.getPath("file2")).thenReturn(cleartextPath2);
+ Mockito.when(cleartextFs.getPath("file3")).thenReturn(cleartextPath3);
+ Mockito.when(cleartextFs.getPath("file1")).thenReturn(cleartextPath1);
Assertions.assertThrows(FileSystemLoopException.class, () -> {
inTest.resolveRecursively(cleartextPath1);
diff --git a/src/test/java/org/cryptomator/cryptofs/attr/AttributeProviderTest.java b/src/test/java/org/cryptomator/cryptofs/attr/AttributeProviderTest.java
index aefa0c92..f9269de1 100644
--- a/src/test/java/org/cryptomator/cryptofs/attr/AttributeProviderTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/attr/AttributeProviderTest.java
@@ -8,7 +8,8 @@
*******************************************************************************/
package org.cryptomator.cryptofs.attr;
-import org.cryptomator.cryptofs.CiphertextFileType;
+import org.cryptomator.cryptofs.CiphertextFilePath;
+import org.cryptomator.cryptofs.common.CiphertextFileType;
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
import org.cryptomator.cryptofs.CryptoPath;
import org.cryptomator.cryptofs.CryptoPathMapper;
@@ -40,7 +41,8 @@ public class AttributeProviderTest {
private OpenCryptoFiles openCryptoFiles;
private CryptoFileSystemProperties fileSystemProperties;
private CryptoPath cleartextPath;
- private Path ciphertextFilePath;
+ private CiphertextFilePath ciphertextPath;
+ private Path ciphertextRawPath;
private Symlinks symlinks;
@BeforeEach
@@ -50,21 +52,26 @@ public void setup() throws IOException {
openCryptoFiles = Mockito.mock(OpenCryptoFiles.class);
fileSystemProperties = Mockito.mock(CryptoFileSystemProperties.class);
cleartextPath = Mockito.mock(CryptoPath.class, "cleartextPath");
- ciphertextFilePath = Mockito.mock(Path.class, "ciphertextPath");
+ ciphertextRawPath = Mockito.mock(Path.class, "ciphertextPath");
+ ciphertextPath = Mockito.mock(CiphertextFilePath.class);
symlinks = Mockito.mock(Symlinks.class);
FileSystem fs = Mockito.mock(FileSystem.class);
- Mockito.when(ciphertextFilePath.getFileSystem()).thenReturn(fs);
+ Mockito.when(ciphertextRawPath.getFileSystem()).thenReturn(fs);
FileSystemProvider provider = Mockito.mock(FileSystemProvider.class);
Mockito.when(fs.provider()).thenReturn(provider);
BasicFileAttributes basicAttr = Mockito.mock(BasicFileAttributes.class);
PosixFileAttributes posixAttr = Mockito.mock(PosixFileAttributes.class);
DosFileAttributes dosAttr = Mockito.mock(DosFileAttributes.class);
- Mockito.when(provider.readAttributes(Mockito.same(ciphertextFilePath), Mockito.same(BasicFileAttributes.class), Mockito.any())).thenReturn(basicAttr);
- Mockito.when(provider.readAttributes(Mockito.same(ciphertextFilePath), Mockito.same(PosixFileAttributes.class), Mockito.any())).thenReturn(posixAttr);
- Mockito.when(provider.readAttributes(Mockito.same(ciphertextFilePath), Mockito.same(DosFileAttributes.class), Mockito.any())).thenReturn(dosAttr);
+ Mockito.when(provider.readAttributes(Mockito.same(ciphertextRawPath), Mockito.same(BasicFileAttributes.class), Mockito.any())).thenReturn(basicAttr);
+ Mockito.when(provider.readAttributes(Mockito.same(ciphertextRawPath), Mockito.same(PosixFileAttributes.class), Mockito.any())).thenReturn(posixAttr);
+ Mockito.when(provider.readAttributes(Mockito.same(ciphertextRawPath), Mockito.same(DosFileAttributes.class), Mockito.any())).thenReturn(dosAttr);
Mockito.when(pathMapper.getCiphertextFileType(cleartextPath)).thenReturn(CiphertextFileType.FILE);
- Mockito.when(pathMapper.getCiphertextFilePath(cleartextPath, CiphertextFileType.FILE)).thenReturn(ciphertextFilePath);
+ Mockito.when(pathMapper.getCiphertextFilePath(cleartextPath)).thenReturn(ciphertextPath);
+ Mockito.when(ciphertextPath.getRawPath()).thenReturn(ciphertextRawPath);
+ Mockito.when(ciphertextPath.getFilePath()).thenReturn(ciphertextRawPath);
+ Mockito.when(ciphertextPath.getDirFilePath()).thenReturn(ciphertextRawPath);
+ Mockito.when(ciphertextPath.getSymlinkFilePath()).thenReturn(ciphertextRawPath);
// needed for cleartxt file size calculation
FileHeaderCryptor fileHeaderCryptor = Mockito.mock(FileHeaderCryptor.class);
@@ -87,7 +94,7 @@ public class Files {
@BeforeEach
public void setup() throws IOException {
Mockito.when(pathMapper.getCiphertextFileType(cleartextPath)).thenReturn(CiphertextFileType.FILE);
- Mockito.when(pathMapper.getCiphertextFilePath(cleartextPath, CiphertextFileType.FILE)).thenReturn(ciphertextFilePath);
+ Mockito.when(pathMapper.getCiphertextFilePath(cleartextPath)).thenReturn(ciphertextPath);
}
@Test
@@ -131,7 +138,7 @@ public class Directories {
@BeforeEach
public void setup() throws IOException {
Mockito.when(pathMapper.getCiphertextFileType(cleartextPath)).thenReturn(CiphertextFileType.DIRECTORY);
- Mockito.when(pathMapper.getCiphertextDir(cleartextPath)).thenReturn(new CiphertextDirectory("foo", ciphertextFilePath));
+ Mockito.when(pathMapper.getCiphertextDir(cleartextPath)).thenReturn(new CiphertextDirectory("foo", ciphertextRawPath));
}
@Test
@@ -154,7 +161,7 @@ public void setup() throws IOException {
@Test
public void testReadBasicAttributesNoFollow() throws IOException {
- Mockito.when(pathMapper.getCiphertextFilePath(cleartextPath, CiphertextFileType.SYMLINK)).thenReturn(ciphertextFilePath);
+ Mockito.when(pathMapper.getCiphertextFilePath(cleartextPath)).thenReturn(ciphertextPath);
AttributeProvider prov = new AttributeProvider(cryptor, pathMapper, openCryptoFiles, fileSystemProperties, symlinks);
BasicFileAttributes attr = prov.readAttributes(cleartextPath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
@@ -167,7 +174,7 @@ public void testReadBasicAttributesOfTarget() throws IOException {
CryptoPath targetPath = Mockito.mock(CryptoPath.class, "targetPath");
Mockito.when(symlinks.resolveRecursively(cleartextPath)).thenReturn(targetPath);
Mockito.when(pathMapper.getCiphertextFileType(targetPath)).thenReturn(CiphertextFileType.FILE);
- Mockito.when(pathMapper.getCiphertextFilePath(targetPath, CiphertextFileType.FILE)).thenReturn(ciphertextFilePath);
+ Mockito.when(pathMapper.getCiphertextFilePath(targetPath)).thenReturn(ciphertextPath);
AttributeProvider prov = new AttributeProvider(cryptor, pathMapper, openCryptoFiles, fileSystemProperties, symlinks);
BasicFileAttributes attr = prov.readAttributes(cleartextPath, BasicFileAttributes.class);
diff --git a/src/test/java/org/cryptomator/cryptofs/attr/CryptoBasicFileAttributesTest.java b/src/test/java/org/cryptomator/cryptofs/attr/CryptoBasicFileAttributesTest.java
index 73cca624..953a4ac7 100644
--- a/src/test/java/org/cryptomator/cryptofs/attr/CryptoBasicFileAttributesTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/attr/CryptoBasicFileAttributesTest.java
@@ -20,9 +20,9 @@
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Optional;
-import static org.cryptomator.cryptofs.CiphertextFileType.DIRECTORY;
-import static org.cryptomator.cryptofs.CiphertextFileType.FILE;
-import static org.cryptomator.cryptofs.CiphertextFileType.SYMLINK;
+import static org.cryptomator.cryptofs.common.CiphertextFileType.DIRECTORY;
+import static org.cryptomator.cryptofs.common.CiphertextFileType.FILE;
+import static org.cryptomator.cryptofs.common.CiphertextFileType.SYMLINK;
public class CryptoBasicFileAttributesTest {
diff --git a/src/test/java/org/cryptomator/cryptofs/attr/CryptoDosFileAttributeViewTest.java b/src/test/java/org/cryptomator/cryptofs/attr/CryptoDosFileAttributeViewTest.java
index ab4beb31..82852965 100644
--- a/src/test/java/org/cryptomator/cryptofs/attr/CryptoDosFileAttributeViewTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/attr/CryptoDosFileAttributeViewTest.java
@@ -1,6 +1,7 @@
package org.cryptomator.cryptofs.attr;
-import org.cryptomator.cryptofs.CiphertextFileType;
+import org.cryptomator.cryptofs.CiphertextFilePath;
+import org.cryptomator.cryptofs.common.CiphertextFileType;
import org.cryptomator.cryptofs.CryptoPath;
import org.cryptomator.cryptofs.CryptoPathMapper;
import org.cryptomator.cryptofs.fh.OpenCryptoFiles;
@@ -27,9 +28,10 @@
public class CryptoDosFileAttributeViewTest {
- private Path linkCiphertextPath = mock(Path.class);
-
- private Path ciphertextPath = mock(Path.class);
+ private CiphertextFilePath linkCiphertextPath = mock(CiphertextFilePath.class);
+ private CiphertextFilePath ciphertextPath = mock(CiphertextFilePath.class);
+ private Path linkCiphertextRawPath = mock(Path.class);
+ private Path ciphertextRawPath = mock(Path.class);
private FileSystem fileSystem = mock(FileSystem.class);
private FileSystemProvider provider = mock(FileSystemProvider.class);
private DosFileAttributeView delegate = mock(DosFileAttributeView.class);
@@ -47,19 +49,22 @@ public class CryptoDosFileAttributeViewTest {
@BeforeEach
public void setup() throws IOException {
- when(linkCiphertextPath.getFileSystem()).thenReturn(fileSystem);
+ when(linkCiphertextRawPath.getFileSystem()).thenReturn(fileSystem);
- when(ciphertextPath.getFileSystem()).thenReturn(fileSystem);
+ when(ciphertextRawPath.getFileSystem()).thenReturn(fileSystem);
when(fileSystem.provider()).thenReturn(provider);
- when(provider.getFileAttributeView(ciphertextPath, DosFileAttributeView.class)).thenReturn(delegate);
- when(provider.getFileAttributeView(ciphertextPath, BasicFileAttributeView.class)).thenReturn(delegate);
- when(provider.getFileAttributeView(linkCiphertextPath, DosFileAttributeView.class)).thenReturn(linkDelegate);
+ when(provider.getFileAttributeView(ciphertextRawPath, DosFileAttributeView.class)).thenReturn(delegate);
+ when(provider.getFileAttributeView(ciphertextRawPath, BasicFileAttributeView.class)).thenReturn(delegate);
+ when(provider.getFileAttributeView(linkCiphertextRawPath, DosFileAttributeView.class)).thenReturn(linkDelegate);
when(symlinks.resolveRecursively(link)).thenReturn(cleartextPath);
when(pathMapper.getCiphertextFileType(link)).thenReturn(CiphertextFileType.SYMLINK);
- when(pathMapper.getCiphertextFilePath(link, CiphertextFileType.SYMLINK)).thenReturn(linkCiphertextPath);
+ when(pathMapper.getCiphertextFilePath(link)).thenReturn(linkCiphertextPath);
when(pathMapper.getCiphertextFileType(cleartextPath)).thenReturn(CiphertextFileType.FILE);
- when(pathMapper.getCiphertextFilePath(cleartextPath, CiphertextFileType.FILE)).thenReturn(ciphertextPath);
+ when(pathMapper.getCiphertextFilePath(cleartextPath)).thenReturn(ciphertextPath);
+
+ when(linkCiphertextPath.getSymlinkFilePath()).thenReturn(linkCiphertextRawPath);
+ when(ciphertextPath.getFilePath()).thenReturn(ciphertextRawPath);
inTest = new CryptoDosFileAttributeView(cleartextPath, pathMapper, new LinkOption[]{}, symlinks, openCryptoFiles, fileAttributeProvider, readonlyFlag);
}
diff --git a/src/test/java/org/cryptomator/cryptofs/attr/CryptoDosFileAttributesTest.java b/src/test/java/org/cryptomator/cryptofs/attr/CryptoDosFileAttributesTest.java
index 39398d01..6cfee36a 100644
--- a/src/test/java/org/cryptomator/cryptofs/attr/CryptoDosFileAttributesTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/attr/CryptoDosFileAttributesTest.java
@@ -16,7 +16,7 @@
import java.nio.file.attribute.DosFileAttributes;
import java.util.Optional;
-import static org.cryptomator.cryptofs.CiphertextFileType.FILE;
+import static org.cryptomator.cryptofs.common.CiphertextFileType.FILE;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
diff --git a/src/test/java/org/cryptomator/cryptofs/attr/CryptoFileOwnerAttributeViewTest.java b/src/test/java/org/cryptomator/cryptofs/attr/CryptoFileOwnerAttributeViewTest.java
index 851c8de1..1ad616ad 100644
--- a/src/test/java/org/cryptomator/cryptofs/attr/CryptoFileOwnerAttributeViewTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/attr/CryptoFileOwnerAttributeViewTest.java
@@ -1,6 +1,7 @@
package org.cryptomator.cryptofs.attr;
-import org.cryptomator.cryptofs.CiphertextFileType;
+import org.cryptomator.cryptofs.CiphertextFilePath;
+import org.cryptomator.cryptofs.common.CiphertextFileType;
import org.cryptomator.cryptofs.CryptoPath;
import org.cryptomator.cryptofs.CryptoPathMapper;
import org.cryptomator.cryptofs.fh.OpenCryptoFiles;
@@ -25,8 +26,10 @@
public class CryptoFileOwnerAttributeViewTest {
- private Path linkCiphertextPath = mock(Path.class);
- private Path ciphertextPath = mock(Path.class);
+ private CiphertextFilePath linkCiphertextPath = mock(CiphertextFilePath.class);
+ private CiphertextFilePath ciphertextPath = mock(CiphertextFilePath.class);
+ private Path linkCiphertextRawPath = mock(Path.class);
+ private Path ciphertextRawPath = mock(Path.class);
private FileSystem fileSystem = mock(FileSystem.class);
private FileSystemProvider provider = mock(FileSystemProvider.class);
private FileOwnerAttributeView delegate = mock(FileOwnerAttributeView.class);
@@ -43,17 +46,20 @@ public class CryptoFileOwnerAttributeViewTest {
@BeforeEach
public void setup() throws IOException {
- when(linkCiphertextPath.getFileSystem()).thenReturn(fileSystem);
- when(ciphertextPath.getFileSystem()).thenReturn(fileSystem);
+ when(linkCiphertextRawPath.getFileSystem()).thenReturn(fileSystem);
+ when(ciphertextRawPath.getFileSystem()).thenReturn(fileSystem);
when(fileSystem.provider()).thenReturn(provider);
- when(provider.getFileAttributeView(ciphertextPath, FileOwnerAttributeView.class)).thenReturn(delegate);
- when(provider.getFileAttributeView(linkCiphertextPath, FileOwnerAttributeView.class)).thenReturn(linkDelegate);
+ when(provider.getFileAttributeView(ciphertextRawPath, FileOwnerAttributeView.class)).thenReturn(delegate);
+ when(provider.getFileAttributeView(linkCiphertextRawPath, FileOwnerAttributeView.class)).thenReturn(linkDelegate);
when(symlinks.resolveRecursively(link)).thenReturn(cleartextPath);
when(pathMapper.getCiphertextFileType(link)).thenReturn(CiphertextFileType.SYMLINK);
- when(pathMapper.getCiphertextFilePath(link, CiphertextFileType.SYMLINK)).thenReturn(linkCiphertextPath);
+ when(pathMapper.getCiphertextFilePath(link)).thenReturn(linkCiphertextPath);
when(pathMapper.getCiphertextFileType(cleartextPath)).thenReturn(CiphertextFileType.FILE);
- when(pathMapper.getCiphertextFilePath(cleartextPath, CiphertextFileType.FILE)).thenReturn(ciphertextPath);
+ when(pathMapper.getCiphertextFilePath(cleartextPath)).thenReturn(ciphertextPath);
+
+ when(linkCiphertextPath.getSymlinkFilePath()).thenReturn(linkCiphertextRawPath);
+ when(ciphertextPath.getFilePath()).thenReturn(ciphertextRawPath);
inTest = new CryptoFileOwnerAttributeView(cleartextPath, pathMapper, new LinkOption[]{}, symlinks, openCryptoFiles, readonlyFlag);
}
diff --git a/src/test/java/org/cryptomator/cryptofs/attr/CryptoPosixFileAttributeViewTest.java b/src/test/java/org/cryptomator/cryptofs/attr/CryptoPosixFileAttributeViewTest.java
index e82fb700..8a6e9e89 100644
--- a/src/test/java/org/cryptomator/cryptofs/attr/CryptoPosixFileAttributeViewTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/attr/CryptoPosixFileAttributeViewTest.java
@@ -1,6 +1,7 @@
package org.cryptomator.cryptofs.attr;
-import org.cryptomator.cryptofs.CiphertextFileType;
+import org.cryptomator.cryptofs.CiphertextFilePath;
+import org.cryptomator.cryptofs.common.CiphertextFileType;
import org.cryptomator.cryptofs.CryptoPath;
import org.cryptomator.cryptofs.CryptoPathMapper;
import org.cryptomator.cryptofs.fh.OpenCryptoFiles;
@@ -34,8 +35,10 @@
public class CryptoPosixFileAttributeViewTest {
- private Path linkCiphertextPath = mock(Path.class);
- private Path ciphertextPath = mock(Path.class);
+ private CiphertextFilePath linkCiphertextPath = mock(CiphertextFilePath.class);
+ private CiphertextFilePath ciphertextPath = mock(CiphertextFilePath.class);
+ private Path linkCiphertextRawPath = mock(Path.class);
+ private Path ciphertextRawPath = mock(Path.class);
private FileSystem fileSystem = mock(FileSystem.class);
private FileSystemProvider provider = mock(FileSystemProvider.class);
private PosixFileAttributeView delegate = mock(PosixFileAttributeView.class);
@@ -53,18 +56,21 @@ public class CryptoPosixFileAttributeViewTest {
@BeforeEach
public void setUp() throws IOException {
- when(linkCiphertextPath.getFileSystem()).thenReturn(fileSystem);
- when(ciphertextPath.getFileSystem()).thenReturn(fileSystem);
+ when(linkCiphertextRawPath.getFileSystem()).thenReturn(fileSystem);
+ when(ciphertextRawPath.getFileSystem()).thenReturn(fileSystem);
when(fileSystem.provider()).thenReturn(provider);
- when(provider.getFileAttributeView(ciphertextPath, PosixFileAttributeView.class)).thenReturn(delegate);
- when(provider.getFileAttributeView(ciphertextPath, BasicFileAttributeView.class)).thenReturn(delegate);
- when(provider.getFileAttributeView(linkCiphertextPath, PosixFileAttributeView.class)).thenReturn(linkDelegate);
+ when(provider.getFileAttributeView(ciphertextRawPath, PosixFileAttributeView.class)).thenReturn(delegate);
+ when(provider.getFileAttributeView(ciphertextRawPath, BasicFileAttributeView.class)).thenReturn(delegate);
+ when(provider.getFileAttributeView(linkCiphertextRawPath, PosixFileAttributeView.class)).thenReturn(linkDelegate);
when(symlinks.resolveRecursively(link)).thenReturn(cleartextPath);
when(pathMapper.getCiphertextFileType(link)).thenReturn(CiphertextFileType.SYMLINK);
- when(pathMapper.getCiphertextFilePath(link, CiphertextFileType.SYMLINK)).thenReturn(linkCiphertextPath);
+ when(pathMapper.getCiphertextFilePath(link)).thenReturn(linkCiphertextPath);
when(pathMapper.getCiphertextFileType(cleartextPath)).thenReturn(CiphertextFileType.FILE);
- when(pathMapper.getCiphertextFilePath(cleartextPath, CiphertextFileType.FILE)).thenReturn(ciphertextPath);
+ when(pathMapper.getCiphertextFilePath(cleartextPath)).thenReturn(ciphertextPath);
+
+ when(linkCiphertextPath.getSymlinkFilePath()).thenReturn(linkCiphertextRawPath);
+ when(ciphertextPath.getFilePath()).thenReturn(ciphertextRawPath);
inTest = new CryptoPosixFileAttributeView(cleartextPath, pathMapper, new LinkOption[]{}, symlinks, openCryptoFiles, fileAttributeProvider, readonlyFlag);
}
diff --git a/src/test/java/org/cryptomator/cryptofs/attr/CryptoPosixFileAttributesTest.java b/src/test/java/org/cryptomator/cryptofs/attr/CryptoPosixFileAttributesTest.java
index 9b869f63..41d12f25 100644
--- a/src/test/java/org/cryptomator/cryptofs/attr/CryptoPosixFileAttributesTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/attr/CryptoPosixFileAttributesTest.java
@@ -21,7 +21,7 @@
import static java.nio.file.attribute.PosixFilePermission.OTHERS_READ;
import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE;
import static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
-import static org.cryptomator.cryptofs.CiphertextFileType.FILE;
+import static org.cryptomator.cryptofs.common.CiphertextFileType.FILE;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
diff --git a/src/test/java/org/cryptomator/cryptofs/AsyncDelegatingFileChannelTest.java b/src/test/java/org/cryptomator/cryptofs/ch/AsyncDelegatingFileChannelTest.java
similarity index 96%
rename from src/test/java/org/cryptomator/cryptofs/AsyncDelegatingFileChannelTest.java
rename to src/test/java/org/cryptomator/cryptofs/ch/AsyncDelegatingFileChannelTest.java
index 3901b704..ce452646 100644
--- a/src/test/java/org/cryptomator/cryptofs/AsyncDelegatingFileChannelTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/ch/AsyncDelegatingFileChannelTest.java
@@ -6,8 +6,9 @@
* Contributors:
* Sebastian Stenzel - initial API and implementation
*******************************************************************************/
-package org.cryptomator.cryptofs;
+package org.cryptomator.cryptofs.ch;
+import org.cryptomator.cryptofs.ch.AsyncDelegatingFileChannel;
import org.hamcrest.CoreMatchers;
import org.hamcrest.MatcherAssert;
import org.junit.jupiter.api.Assertions;
diff --git a/src/test/java/org/cryptomator/cryptofs/FinallyUtilTest.java b/src/test/java/org/cryptomator/cryptofs/common/FinallyUtilTest.java
similarity index 94%
rename from src/test/java/org/cryptomator/cryptofs/FinallyUtilTest.java
rename to src/test/java/org/cryptomator/cryptofs/common/FinallyUtilTest.java
index 4394784f..c4f325b4 100644
--- a/src/test/java/org/cryptomator/cryptofs/FinallyUtilTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/common/FinallyUtilTest.java
@@ -1,5 +1,7 @@
-package org.cryptomator.cryptofs;
+package org.cryptomator.cryptofs.common;
+import org.cryptomator.cryptofs.common.FinallyUtil;
+import org.cryptomator.cryptofs.common.RunnableThrowingException;
import org.junit.jupiter.api.Test;
import org.mockito.InOrder;
diff --git a/src/test/java/org/cryptomator/cryptofs/dir/BrokenDirectoryFilterTest.java b/src/test/java/org/cryptomator/cryptofs/dir/BrokenDirectoryFilterTest.java
new file mode 100644
index 00000000..1741b73b
--- /dev/null
+++ b/src/test/java/org/cryptomator/cryptofs/dir/BrokenDirectoryFilterTest.java
@@ -0,0 +1,55 @@
+package org.cryptomator.cryptofs.dir;
+
+import org.cryptomator.cryptofs.CryptoPathMapper;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.mockito.Mockito;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.stream.Stream;
+
+class BrokenDirectoryFilterTest {
+
+ private CryptoPathMapper cryptoPathMapper = Mockito.mock(CryptoPathMapper.class);
+ private BrokenDirectoryFilter brokenDirectoryFilter = new BrokenDirectoryFilter(cryptoPathMapper);
+
+ @Test
+ public void testProcessNonDirectoryNode(@TempDir Path dir) {
+ Node unfiltered = new Node(dir.resolve("foo.c9r"));
+
+ Stream result = brokenDirectoryFilter.process(unfiltered);
+ Node filtered = result.findAny().get();
+
+ Assertions.assertSame(unfiltered, filtered);
+ }
+
+ @Test
+ public void testProcessNormalDirectoryNode(@TempDir Path dir) throws IOException {
+ Path targetDir = Files.createDirectories(dir.resolve("d/ab/cdefg"));
+ Files.createDirectory(dir.resolve("foo.c9r"));
+ Files.write(dir.resolve("foo.c9r/dir.c9r"), "".getBytes());
+ Mockito.when(cryptoPathMapper.resolveDirectory(Mockito.any())).thenReturn(new CryptoPathMapper.CiphertextDirectory("asd", targetDir));
+ Node unfiltered = new Node(dir.resolve("foo.c9r"));
+
+ Stream result = brokenDirectoryFilter.process(unfiltered);
+ Node filtered = result.findAny().get();
+
+ Assertions.assertSame(unfiltered, filtered);
+ }
+
+ @Test
+ public void testProcessNodeWithMissingTargetDir(@TempDir Path dir) throws IOException {
+ Path targetDir = dir.resolve("d/ab/cdefg"); // not existing!
+ Files.createDirectory(dir.resolve("foo.c9r"));
+ Files.write(dir.resolve("foo.c9r/dir.c9r"), "".getBytes());
+ Mockito.when(cryptoPathMapper.resolveDirectory(Mockito.any())).thenReturn(new CryptoPathMapper.CiphertextDirectory("asd", targetDir));
+ Node unfiltered = new Node(dir.resolve("foo.c9r"));
+
+ Stream result = brokenDirectoryFilter.process(unfiltered);
+ Assertions.assertFalse(result.findAny().isPresent());
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/cryptomator/cryptofs/dir/C9SInflatorTest.java b/src/test/java/org/cryptomator/cryptofs/dir/C9SInflatorTest.java
new file mode 100644
index 00000000..1c771b0c
--- /dev/null
+++ b/src/test/java/org/cryptomator/cryptofs/dir/C9SInflatorTest.java
@@ -0,0 +1,64 @@
+package org.cryptomator.cryptofs.dir;
+
+import org.cryptomator.cryptofs.LongFileNameProvider;
+import org.cryptomator.cryptolib.api.AuthenticationFailedException;
+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.Test;
+import org.mockito.Mockito;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.stream.Stream;
+
+class C9SInflatorTest {
+
+ private LongFileNameProvider longFileNameProvider;
+ private Cryptor cryptor;
+ private FileNameCryptor fileNameCryptor;
+ private C9sInflator inflator;
+
+ @BeforeEach
+ public void setup() {
+ longFileNameProvider = Mockito.mock(LongFileNameProvider.class);
+ cryptor = Mockito.mock(Cryptor.class);
+ fileNameCryptor = Mockito.mock(FileNameCryptor.class);
+ Mockito.when(cryptor.fileNameCryptor()).thenReturn(fileNameCryptor);
+ inflator = new C9sInflator(longFileNameProvider, cryptor, "foo");
+ }
+
+ @Test
+ public void inflateDeflated() throws IOException {
+ Node deflated = new Node(Paths.get("foo.c9s"));
+ Mockito.when(longFileNameProvider.inflate(deflated.ciphertextPath)).thenReturn("foo.c9r");
+ Mockito.when(fileNameCryptor.decryptFilename(Mockito.any(), Mockito.eq("foo"), Mockito.any())).thenReturn("hello world.txt");
+
+ Stream result = inflator.process(deflated);
+ Node inflated = result.findAny().get();
+
+ Assertions.assertEquals("foo", inflated.extractedCiphertext);
+ Assertions.assertEquals("hello world.txt", inflated.cleartextName);
+ }
+
+ @Test
+ public void inflateUninflatableDueToIOException() throws IOException {
+ Node deflated = new Node(Paths.get("foo.c9s"));
+ Mockito.when(longFileNameProvider.inflate(deflated.ciphertextPath)).thenThrow(new IOException("peng!"));
+
+ Stream result = inflator.process(deflated);
+ Assertions.assertFalse(result.findAny().isPresent());
+ }
+
+ @Test
+ public void inflateUninflatableDueToInvalidCiphertext() throws IOException {
+ Node deflated = new Node(Paths.get("foo.c9s"));
+ Mockito.when(longFileNameProvider.inflate(deflated.ciphertextPath)).thenReturn("foo.c9r");
+ Mockito.when(fileNameCryptor.decryptFilename(Mockito.any(), Mockito.eq("foo"), Mockito.any())).thenThrow(new AuthenticationFailedException("peng!"));
+
+ Stream result = inflator.process(deflated);
+ Assertions.assertFalse(result.findAny().isPresent());
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java b/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java
new file mode 100644
index 00000000..a983fc14
--- /dev/null
+++ b/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java
@@ -0,0 +1,116 @@
+package org.cryptomator.cryptofs.dir;
+
+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.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.mockito.Mockito;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.stream.Stream;
+
+class C9rConflictResolverTest {
+
+ private Cryptor cryptor;
+ private FileNameCryptor fileNameCryptor;
+ private C9rConflictResolver conflictResolver;
+
+ @BeforeEach
+ public void setup() {
+ cryptor = Mockito.mock(Cryptor.class);
+ fileNameCryptor = Mockito.mock(FileNameCryptor.class);
+ Mockito.when(cryptor.fileNameCryptor()).thenReturn(fileNameCryptor);
+ conflictResolver = new C9rConflictResolver(cryptor, "foo");
+ }
+
+ @Test
+ public void testResolveNonConflictingNode() {
+ Node unresolved = new Node(Paths.get("foo.c9r"));
+ unresolved.cleartextName = "bar";
+ unresolved.extractedCiphertext = "foo";
+
+ Stream result = conflictResolver.process(unresolved);
+ Node resolved = result.findAny().get();
+
+ Assertions.assertSame(unresolved, resolved);
+ }
+
+ @Test
+ public void testResolveConflictingFileByChoosingNewName(@TempDir Path dir) throws IOException {
+ Files.createFile(dir.resolve("foo (1).c9r"));
+ Files.createFile(dir.resolve("foo.c9r"));
+ Mockito.when(fileNameCryptor.encryptFilename(Mockito.any(), Mockito.any(), Mockito.any())).thenReturn("baz");
+ Node unresolved = new Node(dir.resolve("foo (1).c9r"));
+ unresolved.cleartextName = "bar.txt";
+ unresolved.extractedCiphertext = "foo";
+
+ Stream result = conflictResolver.process(unresolved);
+ Node resolved = result.findAny().get();
+
+ Assertions.assertNotEquals(unresolved, resolved);
+ Assertions.assertEquals("baz.c9r", resolved.fullCiphertextFileName);
+ Assertions.assertEquals("bar (1).txt", resolved.cleartextName);
+ Assertions.assertTrue(Files.exists(resolved.ciphertextPath));
+ Assertions.assertFalse(Files.exists(unresolved.ciphertextPath));
+ }
+
+ @Test
+ public void testResolveConflictingFileTrivially(@TempDir Path dir) throws IOException {
+ Files.createFile(dir.resolve("foo (1).c9r"));
+ Node unresolved = new Node(dir.resolve("foo (1).c9r"));
+ unresolved.cleartextName = "bar";
+ unresolved.extractedCiphertext = "foo";
+
+ Stream result = conflictResolver.process(unresolved);
+ Node resolved = result.findAny().get();
+
+ Assertions.assertNotEquals(unresolved, resolved);
+ Assertions.assertEquals("foo.c9r", resolved.fullCiphertextFileName);
+ Assertions.assertTrue(Files.exists(resolved.ciphertextPath));
+ Assertions.assertFalse(Files.exists(unresolved.ciphertextPath));
+ }
+
+ @Test
+ public void testResolveConflictingDirTrivially(@TempDir Path dir) throws IOException {
+ Files.createDirectory(dir.resolve("foo (1).c9r"));
+ Files.createDirectory(dir.resolve("foo.c9r"));
+ Files.write(dir.resolve("foo (1).c9r/dir.c9r"), "dirid".getBytes());
+ Files.write(dir.resolve("foo.c9r/dir.c9r"), "dirid".getBytes());
+ Node unresolved = new Node(dir.resolve("foo (1).c9r"));
+ unresolved.cleartextName = "bar";
+ unresolved.extractedCiphertext = "foo";
+
+ Stream result = conflictResolver.process(unresolved);
+ Node resolved = result.findAny().get();
+
+ Assertions.assertNotEquals(unresolved, resolved);
+ Assertions.assertEquals("foo.c9r", resolved.fullCiphertextFileName);
+ Assertions.assertTrue(Files.exists(resolved.ciphertextPath));
+ Assertions.assertFalse(Files.exists(unresolved.ciphertextPath));
+ }
+
+ @Test
+ public void testResolveConflictingSymlinkTrivially(@TempDir Path dir) throws IOException {
+ Files.createDirectory(dir.resolve("foo (1).c9r"));
+ Files.createDirectory(dir.resolve("foo.c9r"));
+ Files.write(dir.resolve("foo (1).c9r/symlink.c9r"), "linktarget".getBytes());
+ Files.write(dir.resolve("foo.c9r/symlink.c9r"), "linktarget".getBytes());
+ Node unresolved = new Node(dir.resolve("foo (1).c9r"));
+ unresolved.cleartextName = "bar";
+ unresolved.extractedCiphertext = "foo";
+
+ Stream result = conflictResolver.process(unresolved);
+ Node resolved = result.findAny().get();
+
+ Assertions.assertNotEquals(unresolved, resolved);
+ Assertions.assertEquals("foo.c9r", resolved.fullCiphertextFileName);
+ Assertions.assertTrue(Files.exists(resolved.ciphertextPath));
+ Assertions.assertFalse(Files.exists(unresolved.ciphertextPath));
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/cryptomator/cryptofs/dir/C9rDecryptorTest.java b/src/test/java/org/cryptomator/cryptofs/dir/C9rDecryptorTest.java
new file mode 100644
index 00000000..1d214212
--- /dev/null
+++ b/src/test/java/org/cryptomator/cryptofs/dir/C9rDecryptorTest.java
@@ -0,0 +1,120 @@
+package org.cryptomator.cryptofs.dir;
+
+import org.cryptomator.cryptolib.api.AuthenticationFailedException;
+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.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.Mockito;
+
+import java.nio.file.Paths;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+class C9rDecryptorTest {
+
+ private Cryptor cryptor;
+ private FileNameCryptor fileNameCryptor;
+ private C9rDecryptor decryptor;
+
+ @BeforeEach
+ public void setup() {
+ cryptor = Mockito.mock(Cryptor.class);
+ fileNameCryptor = Mockito.mock(FileNameCryptor.class);
+ Mockito.when(cryptor.fileNameCryptor()).thenReturn(fileNameCryptor);
+ decryptor = new C9rDecryptor(cryptor, "foo");
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "aaaaBBBBccccDDDDeeeeFFFF",
+ "aaaaBBBBccccDDDDeeeeFFF=",
+ "aaaaBBBBccccDDDDeeeeFF==",
+ "aaaaBBBBccccDDDDeeeeF===",
+ "aaaaBBBBccccDDDDeeee====",
+ "aaaaBBBB0123456789-_====",
+ "aaaaBBBBccccDDDDeeeeFFFFggggHH==",
+ })
+ public void testValidBase64Pattern(String input) {
+ Assertions.assertTrue(C9rDecryptor.BASE64_PATTERN.matcher(input).matches());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "aaaaBBBBccccDDDDeeee", // too short
+ "aaaaBBBBccccDDDDeeeeFFF", // unpadded
+ "====BBBBccccDDDDeeeeFFFF", // padding not at end
+ "????BBBBccccDDDDeeeeFFFF", // invalid chars
+ "conflict aaaaBBBBccccDDDDeeeeFFFF", // only a partial match
+ "aaaaBBBBccccDDDDeeeeFFFF conflict", // only a partial match
+ })
+ public void testInvalidBase64Pattern(String input) {
+ Assertions.assertFalse(C9rDecryptor.BASE64_PATTERN.matcher(input).matches());
+ }
+
+ @Test
+ @DisplayName("process canonical filename")
+ public void testProcessFullMatch() {
+ Mockito.when(fileNameCryptor.decryptFilename(Mockito.any(), Mockito.any(), Mockito.any())).thenReturn("helloWorld.txt");
+ Node input = new Node(Paths.get("aaaaBBBBccccDDDDeeeeFFFF.c9r"));
+
+ Stream resultStream = decryptor.process(input);
+ Optional optionalResult = resultStream.findAny();
+
+ Assertions.assertTrue(optionalResult.isPresent());
+ Assertions.assertEquals("helloWorld.txt", optionalResult.get().cleartextName);
+ }
+
+ @DisplayName("process non-canonical filename")
+ @ParameterizedTest(name = "{0}")
+ @ValueSource(strings = {
+ "aaaaBBBBcccc_--_11112222 (conflict3000).c9r",
+ "(conflict3000) aaaaBBBBcccc_--_11112222.c9r",
+ "conflict_aaaaBBBBcccc_--_11112222.c9r",
+ "aaaaBBBBcccc_--_11112222_conflict.c9r",
+ "____aaaaBBBBcccc_--_11112222.c9r",
+ "aaaaBBBBcccc_--_11112222____.c9r",
+ "foo_aaaaBBBBcccc_--_11112222_foo.c9r",
+ "aaaaBBBBccccDDDDeeeeFFFF___aaaaBBBBcccc_--_11112222----aaaaBBBBccccDDDDeeeeFFFF.c9r",
+ })
+ public void testProcessPartialMatch(String filename) {
+ Mockito.when(fileNameCryptor.decryptFilename(Mockito.any(), Mockito.any(), Mockito.any())).then(invocation -> {
+ String ciphertext = invocation.getArgument(1);
+ if (ciphertext.equals("aaaaBBBBcccc_--_11112222")) {
+ return "helloWorld.txt";
+ } else {
+ throw new AuthenticationFailedException("Invalid ciphertext " + ciphertext);
+ }
+ });
+ Node input = new Node(Paths.get(filename));
+
+ Stream resultStream = decryptor.process(input);
+ Optional optionalResult = resultStream.findAny();
+
+ Assertions.assertTrue(optionalResult.isPresent());
+ Assertions.assertEquals("helloWorld.txt", optionalResult.get().cleartextName);
+ }
+
+ @DisplayName("process filename without ciphertext")
+ @ParameterizedTest(name = "{0}")
+ @ValueSource(strings = {
+ "foo.bar",
+ "foo.c9r",
+ "aaaaBBBB????DDDDeeeeFFFF.c9r",
+ "aaaaBBBBxxxxDDDDeeeeFFFF.c9r",
+ })
+ public void testProcessNoMatch(String filename) {
+ Mockito.when(fileNameCryptor.decryptFilename(Mockito.any(), Mockito.any(), Mockito.any())).thenThrow(new AuthenticationFailedException("Invalid ciphertext."));
+ Node input = new Node(Paths.get(filename));
+
+ Stream resultStream = decryptor.process(input);
+ Optional optionalResult = resultStream.findAny();
+
+ Assertions.assertFalse(optionalResult.isPresent());
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/cryptomator/cryptofs/dir/CryptoDirectoryStreamTest.java b/src/test/java/org/cryptomator/cryptofs/dir/CryptoDirectoryStreamTest.java
new file mode 100644
index 00000000..c7c2f0d6
--- /dev/null
+++ b/src/test/java/org/cryptomator/cryptofs/dir/CryptoDirectoryStreamTest.java
@@ -0,0 +1,103 @@
+/*******************************************************************************
+ * 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.dir;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mockito;
+
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.DirectoryStream.Filter;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Spliterators;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+public class CryptoDirectoryStreamTest {
+
+ private static final Consumer DO_NOTHING_ON_CLOSE = ignored -> {
+ };
+ private static final Filter super Path> ACCEPT_ALL = ignored -> true;
+
+ private NodeProcessor nodeProcessor;
+ private DirectoryStream dirStream;
+
+ @BeforeEach
+ public void setup() throws IOException {
+ nodeProcessor = Mockito.mock(NodeProcessor.class);
+ dirStream = Mockito.mock(DirectoryStream.class);
+ }
+
+ @Test
+ public void testDirListing() throws IOException {
+ Path ciphertextPath = Paths.get("/f00/b4r");
+ Path cleartextPath = Paths.get("/foo/bar");
+ List ciphertextFileNames = new ArrayList<>();
+ ciphertextFileNames.add("ciphertextFile1");
+ ciphertextFileNames.add("ciphertextFile2");
+ ciphertextFileNames.add("ciphertextDirectory1");
+ ciphertextFileNames.add("invalidCiphertext");
+ Mockito.when(dirStream.spliterator()).thenReturn(ciphertextFileNames.stream().map(ciphertextPath::resolve).spliterator());
+ Mockito.doAnswer(invocation -> {
+ Node node = invocation.getArgument(0);
+ node.cleartextName = "cleartextFile1";
+ return Stream.of(node);
+ }).when(nodeProcessor).process(Mockito.argThat(node -> node.fullCiphertextFileName.equals("ciphertextFile1")));
+ Mockito.doAnswer(invocation -> {
+ Node node = invocation.getArgument(0);
+ node.cleartextName = "cleartextFile2";
+ return Stream.of(node);
+ }).when(nodeProcessor).process(Mockito.argThat(node -> node.fullCiphertextFileName.equals("ciphertextFile2")));
+ Mockito.doAnswer(invocation -> {
+ Node node = invocation.getArgument(0);
+ node.cleartextName = "cleartextDirectory1";
+ return Stream.of(node);
+ }).when(nodeProcessor).process(Mockito.argThat(node -> node.fullCiphertextFileName.equals("ciphertextDirectory1")));
+ Mockito.doAnswer(invocation -> {
+ return Stream.empty();
+ }).when(nodeProcessor).process(Mockito.argThat(node -> node.fullCiphertextFileName.equals("invalidCiphertext")));
+
+ try (CryptoDirectoryStream stream = new CryptoDirectoryStream("foo", dirStream, cleartextPath, ACCEPT_ALL, DO_NOTHING_ON_CLOSE, nodeProcessor)) {
+ Iterator iter = stream.iterator();
+ Assertions.assertTrue(iter.hasNext());
+ Assertions.assertEquals(cleartextPath.resolve("cleartextFile1"), iter.next());
+ Assertions.assertTrue(iter.hasNext());
+ Assertions.assertEquals(cleartextPath.resolve("cleartextFile2"), iter.next());
+ Assertions.assertTrue(iter.hasNext());
+ Assertions.assertEquals(cleartextPath.resolve("cleartextDirectory1"), iter.next());
+ Assertions.assertFalse(iter.hasNext());
+ Mockito.verify(dirStream, Mockito.never()).close();
+ }
+ Mockito.verify(dirStream).close();
+ }
+
+ @Test
+ public void testDirListingForEmptyDir() throws IOException {
+ Path cleartextPath = Paths.get("/foo/bar");
+
+ Mockito.when(dirStream.spliterator()).thenReturn(Spliterators.emptySpliterator());
+
+ try (CryptoDirectoryStream stream = new CryptoDirectoryStream("foo", dirStream, cleartextPath, ACCEPT_ALL, DO_NOTHING_ON_CLOSE, nodeProcessor)) {
+ Iterator iter = stream.iterator();
+ Assertions.assertFalse(iter.hasNext());
+ Assertions.assertThrows(NoSuchElementException.class, () -> {
+ iter.next();
+ });
+ }
+ }
+
+}
diff --git a/src/test/java/org/cryptomator/cryptofs/DirectoryStreamFactoryTest.java b/src/test/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactoryTest.java
similarity index 60%
rename from src/test/java/org/cryptomator/cryptofs/DirectoryStreamFactoryTest.java
rename to src/test/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactoryTest.java
index 8a057568..565f4947 100644
--- a/src/test/java/org/cryptomator/cryptofs/DirectoryStreamFactoryTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactoryTest.java
@@ -1,10 +1,12 @@
-package org.cryptomator.cryptofs;
+package org.cryptomator.cryptofs.dir;
+import org.cryptomator.cryptofs.CryptoPath;
+import org.cryptomator.cryptofs.CryptoPathMapper;
import org.cryptomator.cryptofs.CryptoPathMapper.CiphertextDirectory;
-import org.cryptomator.cryptolib.api.Cryptor;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
import java.io.IOException;
import java.nio.file.ClosedFileSystemException;
@@ -13,46 +15,35 @@
import java.nio.file.FileSystem;
import java.nio.file.Path;
import java.nio.file.spi.FileSystemProvider;
-import java.util.Iterator;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.same;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class DirectoryStreamFactoryTest {
- private final FileSystem fileSystem = mock(FileSystem.class);
- private final FileSystemProvider provider = mock(FileSystemProvider.class);
- private final FinallyUtil finallyUtil = mock(FinallyUtil.class);
- private final Cryptor cryptor = mock(Cryptor.class);
- private final LongFileNameProvider longFileNameProvider = mock(LongFileNameProvider.class);
- private final ConflictResolver conflictResolver = mock(ConflictResolver.class);
+ private final FileSystem fileSystem = mock(FileSystem.class, "fs");
+ private final FileSystemProvider provider = mock(FileSystemProvider.class, "provider");
private final CryptoPathMapper cryptoPathMapper = mock(CryptoPathMapper.class);
- private final EncryptedNamePattern encryptedNamePattern = new EncryptedNamePattern();
+ private final DirectoryStreamComponent directoryStreamComp = mock(DirectoryStreamComponent.class);
+ private final DirectoryStreamComponent.Builder directoryStreamBuilder = mock(DirectoryStreamComponent.Builder.class);
- private final DirectoryStreamFactory inTest = new DirectoryStreamFactory(cryptor, longFileNameProvider, conflictResolver, cryptoPathMapper, finallyUtil, encryptedNamePattern);
+ private final DirectoryStreamFactory inTest = new DirectoryStreamFactory(cryptoPathMapper, directoryStreamBuilder);
@SuppressWarnings("unchecked")
@BeforeEach
- public void setup() {
- doAnswer(invocation -> {
- for (Object runnable : invocation.getArguments()) {
- ((RunnableThrowingException>) runnable).run();
- }
- return null;
- }).when(finallyUtil).guaranteeInvocationOf(any(RunnableThrowingException.class), any(RunnableThrowingException.class), any(RunnableThrowingException.class));
- doAnswer(invocation -> {
- Iterator> iterator = invocation.getArgument(0);
- while (iterator.hasNext()) {
- iterator.next().run();
- }
- return null;
- }).when(finallyUtil).guaranteeInvocationOf(any(Iterator.class));
+ public void setup() throws IOException {
+ when(directoryStreamBuilder.cleartextPath(Mockito.any())).thenReturn(directoryStreamBuilder);
+ when(directoryStreamBuilder.dirId(Mockito.any())).thenReturn(directoryStreamBuilder);
+ when(directoryStreamBuilder.ciphertextDirectoryStream(Mockito.any())).thenReturn(directoryStreamBuilder);
+ when(directoryStreamBuilder.filter(Mockito.any())).thenReturn(directoryStreamBuilder);
+ when(directoryStreamBuilder.onClose(Mockito.any())).thenReturn(directoryStreamBuilder);
+ when(directoryStreamBuilder.build()).thenReturn(directoryStreamComp);
+ when(directoryStreamComp.directoryStream()).then(invocation -> mock(CryptoDirectoryStream.class));
when(fileSystem.provider()).thenReturn(provider);
}
@@ -62,9 +53,11 @@ public void testNewDirectoryStreamCreatesDirectoryStream() throws IOException {
CryptoPath path = mock(CryptoPath.class);
Filter super Path> filter = mock(Filter.class);
String dirId = "dirIdAbc";
- Path dirPath = mock(Path.class);
+ Path dirPath = mock(Path.class, "dirAbc");
when(dirPath.getFileSystem()).thenReturn(fileSystem);
when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CiphertextDirectory(dirId, dirPath));
+ DirectoryStream stream = mock(DirectoryStream.class);
+ when(provider.newDirectoryStream(same(dirPath), any())).thenReturn(stream);
DirectoryStream directoryStream = inTest.newDirectoryStream(path, filter);
@@ -75,16 +68,16 @@ public void testNewDirectoryStreamCreatesDirectoryStream() throws IOException {
@Test
public void testCloseClosesAllNonClosedDirectoryStreams() throws IOException {
Filter super Path> filter = mock(Filter.class);
- CryptoPath pathA = mock(CryptoPath.class);
- CryptoPath pathB = mock(CryptoPath.class);
- Path dirPathA = mock(Path.class);
+ CryptoPath pathA = mock(CryptoPath.class, "pathA");
+ CryptoPath pathB = mock(CryptoPath.class, "pathB");
+ Path dirPathA = mock(Path.class, "dirPathA");
when(dirPathA.getFileSystem()).thenReturn(fileSystem);
- Path dirPathB = mock(Path.class);
+ Path dirPathB = mock(Path.class, "dirPathB");
when(dirPathB.getFileSystem()).thenReturn(fileSystem);
when(cryptoPathMapper.getCiphertextDir(pathA)).thenReturn(new CiphertextDirectory("dirIdA", dirPathA));
when(cryptoPathMapper.getCiphertextDir(pathB)).thenReturn(new CiphertextDirectory("dirIdB", dirPathB));
- DirectoryStream streamA = mock(DirectoryStream.class);
- DirectoryStream streamB = mock(DirectoryStream.class);
+ DirectoryStream streamA = mock(DirectoryStream.class, "streamA");
+ DirectoryStream streamB = mock(DirectoryStream.class, "streamB");
when(provider.newDirectoryStream(same(dirPathA), any())).thenReturn(streamA);
when(provider.newDirectoryStream(same(dirPathB), any())).thenReturn(streamB);
@@ -102,13 +95,9 @@ public void testCloseClosesAllNonClosedDirectoryStreams() throws IOException {
public void testNewDirectoryStreamAfterClosedThrowsClosedFileSystemException() throws IOException {
CryptoPath path = mock(CryptoPath.class);
Filter super Path> filter = mock(Filter.class);
- String dirId = "dirIdAbc";
- Path dirPath = mock(Path.class);
- when(dirPath.getFileSystem()).thenReturn(fileSystem);
- when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CiphertextDirectory(dirId, dirPath));
- when(provider.newDirectoryStream(same(dirPath), any())).thenReturn(mock(DirectoryStream.class));
-
+
inTest.close();
+
Assertions.assertThrows(ClosedFileSystemException.class, () -> {
inTest.newDirectoryStream(path, filter);
});
diff --git a/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFilesTest.java b/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFilesTest.java
index 673fe75e..f043e701 100644
--- a/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFilesTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFilesTest.java
@@ -25,7 +25,6 @@
public class OpenCryptoFilesTest {
- private final CryptoFileSystemComponent cryptoFileSystemComponent = mock(CryptoFileSystemComponent.class);
private final OpenCryptoFileComponent.Builder openCryptoFileComponentBuilder = mock(OpenCryptoFileComponent.Builder.class);
private final OpenCryptoFile file = mock(OpenCryptoFile.class, "file");
private final FileChannel ciphertextFileChannel = Mockito.mock(FileChannel.class);
@@ -37,7 +36,6 @@ public void setup() throws IOException, ReflectiveOperationException {
OpenCryptoFileComponent subComponent = mock(OpenCryptoFileComponent.class);
Mockito.when(subComponent.openCryptoFile()).thenReturn(file);
- Mockito.when(cryptoFileSystemComponent.newOpenCryptoFileComponent()).thenReturn(openCryptoFileComponentBuilder);
Mockito.when(openCryptoFileComponentBuilder.path(Mockito.any())).thenReturn(openCryptoFileComponentBuilder);
Mockito.when(openCryptoFileComponentBuilder.onClose(Mockito.any())).thenReturn(openCryptoFileComponentBuilder);
Mockito.when(openCryptoFileComponentBuilder.build()).thenReturn(subComponent);
@@ -47,7 +45,7 @@ public void setup() throws IOException, ReflectiveOperationException {
closeLockField.setAccessible(true);
closeLockField.set(ciphertextFileChannel, new Object());
- inTest = new OpenCryptoFiles(cryptoFileSystemComponent);
+ inTest = new OpenCryptoFiles(openCryptoFileComponentBuilder);
}
@Test
@@ -60,7 +58,6 @@ public void testGetOrCreate() {
OpenCryptoFile file2 = mock(OpenCryptoFile.class);
Mockito.when(subComponent2.openCryptoFile()).thenReturn(file2);
- Mockito.when(cryptoFileSystemComponent.newOpenCryptoFileComponent()).thenReturn(openCryptoFileComponentBuilder);
Mockito.when(openCryptoFileComponentBuilder.build()).thenReturn(subComponent1, subComponent2);
Path p1 = Paths.get("/foo");
diff --git a/src/test/java/org/cryptomator/cryptofs/migration/MigratorsTest.java b/src/test/java/org/cryptomator/cryptofs/migration/MigratorsTest.java
index d0e5b429..df8f3e29 100644
--- a/src/test/java/org/cryptomator/cryptofs/migration/MigratorsTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/migration/MigratorsTest.java
@@ -5,6 +5,7 @@
*******************************************************************************/
package org.cryptomator.cryptofs.migration;
+import org.cryptomator.cryptofs.migration.api.MigrationProgressListener;
import org.cryptomator.cryptofs.migration.api.Migrator;
import org.cryptomator.cryptofs.migration.api.NoApplicableMigratorException;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
@@ -76,21 +77,22 @@ public void testNeedsNoMigration() throws IOException {
public void testMigrateWithoutMigrators() throws IOException {
Migrators migrators = new Migrators(Collections.emptyMap());
Assertions.assertThrows(NoApplicableMigratorException.class, () -> {
- migrators.migrate(pathToVault, "masterkey.cryptomator", "secret");
+ migrators.migrate(pathToVault, "masterkey.cryptomator", "secret", (state, progress) -> {});
});
}
@Test
@SuppressWarnings("deprecation")
public void testMigrate() throws NoApplicableMigratorException, InvalidPassphraseException, IOException {
+ MigrationProgressListener listener = Mockito.mock(MigrationProgressListener.class);
Migrator migrator = Mockito.mock(Migrator.class);
Migrators migrators = new Migrators(new HashMap() {
{
put(Migration.ZERO_TO_ONE, migrator);
}
});
- migrators.migrate(pathToVault, "masterkey.cryptomator", "secret");
- Mockito.verify(migrator).migrate(pathToVault, "masterkey.cryptomator", "secret");
+ migrators.migrate(pathToVault, "masterkey.cryptomator", "secret", listener);
+ Mockito.verify(migrator).migrate(pathToVault, "masterkey.cryptomator", "secret", listener);
}
@Test
@@ -102,9 +104,9 @@ public void testMigrateUnsupportedVaultFormat() throws NoApplicableMigratorExcep
put(Migration.ZERO_TO_ONE, migrator);
}
});
- Mockito.doThrow(new UnsupportedVaultFormatException(Integer.MAX_VALUE, 1)).when(migrator).migrate(pathToVault, "masterkey.cryptomator", "secret");
+ Mockito.doThrow(new UnsupportedVaultFormatException(Integer.MAX_VALUE, 1)).when(migrator).migrate(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
Assertions.assertThrows(IllegalStateException.class, () -> {
- migrators.migrate(pathToVault, "masterkey.cryptomator", "secret");
+ migrators.migrate(pathToVault, "masterkey.cryptomator", "secret", (state, progress) -> {});
});
}
diff --git a/src/test/java/org/cryptomator/cryptofs/migration/v6/Version6MigratorTest.java b/src/test/java/org/cryptomator/cryptofs/migration/v6/Version6MigratorTest.java
index 8552049d..74d38267 100644
--- a/src/test/java/org/cryptomator/cryptofs/migration/v6/Version6MigratorTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/migration/v6/Version6MigratorTest.java
@@ -2,8 +2,8 @@
import com.google.common.jimfs.Configuration;
import com.google.common.jimfs.Jimfs;
-import org.cryptomator.cryptofs.BackupUtil;
-import org.cryptomator.cryptofs.Constants;
+import org.cryptomator.cryptofs.common.MasterkeyBackupFileHasher;
+import org.cryptomator.cryptofs.common.Constants;
import org.cryptomator.cryptofs.migration.api.Migrator;
import org.cryptomator.cryptofs.mocks.NullSecureRandom;
import org.cryptomator.cryptolib.Cryptors;
@@ -53,7 +53,7 @@ public void testMigrate() throws IOException {
KeyFile beforeMigration = cryptorProvider.createNew().writeKeysToMasterkeyFile(oldPassword, 5);
Assertions.assertEquals(5, beforeMigration.getVersion());
Files.write(masterkeyFile, beforeMigration.serialize());
- Path masterkeyBackupFile = pathToVault.resolve("masterkey.cryptomator" + BackupUtil.generateFileIdSuffix(beforeMigration.serialize()) + Constants.MASTERKEY_BACKUP_SUFFIX);
+ Path masterkeyBackupFile = pathToVault.resolve("masterkey.cryptomator" + MasterkeyBackupFileHasher.generateFileIdSuffix(beforeMigration.serialize()) + Constants.MASTERKEY_BACKUP_SUFFIX);
Migrator migrator = new Version6Migrator(cryptorProvider);
migrator.migrate(pathToVault, "masterkey.cryptomator", oldPassword);
diff --git a/src/test/java/org/cryptomator/cryptofs/migration/v7/FilePathMigrationTest.java b/src/test/java/org/cryptomator/cryptofs/migration/v7/FilePathMigrationTest.java
new file mode 100644
index 00000000..e2962915
--- /dev/null
+++ b/src/test/java/org/cryptomator/cryptofs/migration/v7/FilePathMigrationTest.java
@@ -0,0 +1,328 @@
+package org.cryptomator.cryptofs.migration.v7;
+
+import com.google.common.io.MoreFiles;
+import com.google.common.io.RecursiveDeleteOption;
+import com.google.common.jimfs.Configuration;
+import com.google.common.jimfs.Jimfs;
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.MatcherAssert;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+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.TestInstance;
+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.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Optional;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+public class FilePathMigrationTest {
+
+ private Path oldPath = Mockito.mock(Path.class, "oldPath");
+
+ @ParameterizedTest(name = "getOldCanonicalNameWithoutTypePrefix() expected to be {1} for {0}")
+ @CsvSource({
+ "ORSXG5A=,ORSXG5A=",
+ "0ORSXG5A=,ORSXG5A=",
+ "1SORSXG5A=,ORSXG5A=",
+ })
+ public void testGetOldCanonicalNameWithoutTypePrefix(String oldCanonicalName, String expectedResult) {
+ FilePathMigration migration = new FilePathMigration(oldPath, oldCanonicalName);
+
+ Assertions.assertEquals(expectedResult, migration.getOldCanonicalNameWithoutTypePrefix());
+ }
+
+ @ParameterizedTest(name = "getNewInflatedName() expected to be {1} for {0}")
+ @CsvSource({
+ "ORSXG5A=,dGVzdA==.c9r",
+ "0ORSXG5A=,dGVzdA==.c9r",
+ "1SORSXG5A=,dGVzdA==.c9r",
+ })
+ public void testGetNewInflatedName(String oldCanonicalName, String expectedResult) {
+ FilePathMigration migration = new FilePathMigration(oldPath, oldCanonicalName);
+
+ Assertions.assertEquals(expectedResult, migration.getNewInflatedName());
+ }
+
+ @ParameterizedTest(name = "getNewInflatedName() expected to be {1} for {0}")
+ @CsvSource({
+ "ORSXG5A=,dGVzdA==.c9r",
+ "ORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSQ====,dGVzdCB0ZXN0IHRlc3QgdGVzdCB0ZXN0IHRlc3QgdGVzdCB0ZXN0IHRlc3QgdGVzdCB0ZXN0IHRlc3QgdGVzdCB0ZXN0IHRlc3QgdGVzdCB0ZXN0IHRlc3QgdGVzdCB0ZXN0IHRlc3QgdGVzdCB0ZXN0IHRlc3QgdGVzdCB0ZXN0IHRlc3QgdGVzdCB0ZXN0IHRlc3QgdGVzdCB0ZXN0IHRl.c9r",
+ "ORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG===,30xtS3YjsiMJRwu1oAVc_0S2aAU=.c9s",
+ })
+ public void testGetNewDeflatedName(String oldCanonicalName, String expectedResult) {
+ FilePathMigration migration = new FilePathMigration(oldPath, oldCanonicalName);
+
+ Assertions.assertEquals(expectedResult, migration.getNewDeflatedName());
+ }
+
+ @ParameterizedTest(name = "isDirectory() expected to be {1} for {0}")
+ @CsvSource({
+ "ORSXG5A=,false",
+ "0ORSXG5A=,true",
+ "1SORSXG5A=,false",
+ })
+ public void testIsDirectory(String oldCanonicalName, boolean expectedResult) {
+ FilePathMigration migration = new FilePathMigration(oldPath, oldCanonicalName);
+
+ Assertions.assertEquals(expectedResult, migration.isDirectory());
+ }
+
+ @ParameterizedTest(name = "isSymlink() expected to be {1} for {0}")
+ @CsvSource({
+ "ORSXG5A=,false",
+ "0ORSXG5A=,false",
+ "1SORSXG5A=,true",
+ })
+ public void testIsSymlink(String oldCanonicalName, boolean expectedResult) {
+ FilePathMigration migration = new FilePathMigration(oldPath, oldCanonicalName);
+
+ Assertions.assertEquals(expectedResult, migration.isSymlink());
+ }
+
+ @ParameterizedTest(name = "getTargetPath() expected to be {1} for {0}")
+ @CsvSource({
+ "ORSXG5A=,'',dGVzdA==.c9r",
+ "0ORSXG5A=,'',dGVzdA==.c9r/dir.c9r",
+ "1SORSXG5A=,'',dGVzdA==.c9r/symlink.c9r",
+ "ORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG===,'',30xtS3YjsiMJRwu1oAVc_0S2aAU=.c9s/contents.c9r",
+ "0ORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG===,'',30xtS3YjsiMJRwu1oAVc_0S2aAU=.c9s/dir.c9r",
+ "1SORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG===,'',30xtS3YjsiMJRwu1oAVc_0S2aAU=.c9s/symlink.c9r",
+ "ORSXG5A=,'_1',dGVzdA==_1.c9r",
+ "ORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG===,'_123',30xtS3YjsiMJRwu1oAVc_0S2aAU=_123.c9s/contents.c9r",
+ })
+ public void testGetTargetPath(String oldCanonicalName, String attemptSuffix, String expected) {
+ Path old = Paths.get("/tmp/foo");
+ FilePathMigration migration = new FilePathMigration(old, oldCanonicalName);
+
+ Path result = migration.getTargetPath(attemptSuffix);
+
+ Assertions.assertEquals(old.resolveSibling(expected), result);
+ }
+
+ @DisplayName("FilePathMigration.parse(...)")
+ @Nested
+ @TestInstance(TestInstance.Lifecycle.PER_CLASS)
+ class Parsing {
+
+ private FileSystem fs;
+ private Path vaultRoot;
+ private Path dataDir;
+ private Path metaDir;
+
+ @BeforeAll
+ public void beforeAll() {
+ fs = Jimfs.newFileSystem(Configuration.unix());
+ vaultRoot = fs.getPath("/vaultDir");
+ dataDir = vaultRoot.resolve("d");
+ metaDir = vaultRoot.resolve("m");
+ }
+
+ @BeforeEach
+ public void beforeEach() throws IOException {
+ Files.createDirectory(vaultRoot);
+ Files.createDirectory(dataDir);
+ Files.createDirectory(metaDir);
+ }
+
+ @AfterEach
+ public void afterEach() throws IOException {
+ MoreFiles.deleteRecursively(vaultRoot, RecursiveDeleteOption.ALLOW_INSECURE);
+ }
+
+ @DisplayName("inflate with non-existing metadata file")
+ @Test
+ public void testInflateWithMissingMetadata() {
+ UninflatableFileException e = Assertions.assertThrows(UninflatableFileException.class, () -> {
+ FilePathMigration.inflate(vaultRoot, "NTJDZUB3J5S25LGO7CD4TE5VOJCSW7HF.lng");
+
+ });
+ MatcherAssert.assertThat(e.getCause(), CoreMatchers.instanceOf(NoSuchFileException.class));
+ }
+
+ @DisplayName("inflate with too large metadata file")
+ @Test
+ public void testInflateWithTooLargeMetadata() throws IOException {
+ Path lngFilePath = metaDir.resolve("NT/JD/NTJDZUB3J5S25LGO7CD4TE5VOJCSW7HF.lng");
+ Files.createDirectories(lngFilePath.getParent());
+ Files.write(lngFilePath, new byte[10 * 1024 + 1]);
+
+ UninflatableFileException e = Assertions.assertThrows(UninflatableFileException.class, () -> {
+ FilePathMigration.inflate(vaultRoot, "NTJDZUB3J5S25LGO7CD4TE5VOJCSW7HF.lng");
+
+ });
+ MatcherAssert.assertThat(e.getMessage(), CoreMatchers.containsString("Unexpectedly large file"));
+ }
+
+ @DisplayName("inflate")
+ @ParameterizedTest(name = "inflate(vaultRoot, {0})")
+ @CsvSource({
+ "NTJDZUB3J5S25LGO7CD4TE5VOJCSW7HF.lng,NT/JD/NTJDZUB3J5S25LGO7CD4TE5VOJCSW7HF.lng,ORSXG5A=",
+ "ZNPCXPWRWYFOGTZHVDBOOQDYPAMKKI5R.lng,ZN/PC/ZNPCXPWRWYFOGTZHVDBOOQDYPAMKKI5R.lng,0ORSXG5A=",
+ "NUC3VFSMWKLD4526JDZKSE5V2IIMSYW5.lng,NU/C3/NUC3VFSMWKLD4526JDZKSE5V2IIMSYW5.lng,1SORSXG5A=",
+ })
+ public void testInflate(String canonicalLongFileName, String metadataFilePath, String expected) throws IOException {
+ Path lngFilePath = metaDir.resolve(metadataFilePath);
+ Files.createDirectories(lngFilePath.getParent());
+ Files.write(lngFilePath, expected.getBytes(UTF_8));
+
+ String result = FilePathMigration.inflate(vaultRoot, canonicalLongFileName);
+
+ Assertions.assertEquals(expected, result);
+ }
+
+ @DisplayName("unrelated files")
+ @ParameterizedTest(name = "parse(vaultRoot, {0}) expected to be unparsable")
+ @ValueSource(strings = {
+ "00/000000000000000000000000000000/.DS_Store",
+ "00/000000000000000000000000000000/foo",
+ "00/000000000000000000000000000000/ORSXG5A=.c9r", // already migrated
+ "00/000000000000000000000000000000/ORSXG5A=.c9s", // already migrated
+ "00/000000000000000000000000000000/ORSXG5A", // removed one char
+ "00/000000000000000000000000000000/NTJDZUB3J5S25LGO7CD4TE5VOJCSW7H.lng", // removed one char
+ })
+ public void testParseUnrelatedFile(String oldPath) throws IOException {
+ Path path = dataDir.resolve(oldPath);
+
+ Optional migration = FilePathMigration.parse(vaultRoot, path);
+
+ Assertions.assertFalse(migration.isPresent());
+ }
+
+ @DisplayName("regular files")
+ @ParameterizedTest(name = "parse(vaultRoot, {0}).getOldCanonicalName() expected to be {1}")
+ @CsvSource({
+ "00/000000000000000000000000000000/ORSXG5A=,ORSXG5A=",
+ "00/000000000000000000000000000000/0ORSXG5A=,0ORSXG5A=",
+ "00/000000000000000000000000000000/1SORSXG5A=,1SORSXG5A=",
+ "00/000000000000000000000000000000/conflict_1SORSXG5A=,1SORSXG5A=",
+ "00/000000000000000000000000000000/1SORSXG5A= (conflict),1SORSXG5A=",
+ })
+ public void testParseNonShortenedFile(String oldPath, String expected) throws IOException {
+ Path path = dataDir.resolve(oldPath);
+
+ Optional migration = FilePathMigration.parse(vaultRoot, path);
+
+ Assertions.assertTrue(migration.isPresent());
+ Assertions.assertEquals(expected, migration.get().getOldCanonicalName());
+ }
+
+ @DisplayName("shortened files")
+ @ParameterizedTest(name = "parse(vaultRoot, {0}).getOldCanonicalName() expected to be {2}")
+ @CsvSource({
+ "00/000000000000000000000000000000/NTJDZUB3J5S25LGO7CD4TE5VOJCSW7HF.lng,NT/JD/NTJDZUB3J5S25LGO7CD4TE5VOJCSW7HF.lng,ORSXG5A=",
+ "00/000000000000000000000000000000/ZNPCXPWRWYFOGTZHVDBOOQDYPAMKKI5R.lng,ZN/PC/ZNPCXPWRWYFOGTZHVDBOOQDYPAMKKI5R.lng,0ORSXG5A=",
+ "00/000000000000000000000000000000/NUC3VFSMWKLD4526JDZKSE5V2IIMSYW5.lng,NU/C3/NUC3VFSMWKLD4526JDZKSE5V2IIMSYW5.lng,1SORSXG5A=",
+ "00/000000000000000000000000000000/conflict_NUC3VFSMWKLD4526JDZKSE5V2IIMSYW5.lng,NU/C3/NUC3VFSMWKLD4526JDZKSE5V2IIMSYW5.lng,1SORSXG5A=",
+ "00/000000000000000000000000000000/NUC3VFSMWKLD4526JDZKSE5V2IIMSYW5 (conflict).lng,NU/C3/NUC3VFSMWKLD4526JDZKSE5V2IIMSYW5.lng,1SORSXG5A=",
+ })
+ public void testParseShortenedFile(String oldPath, String metadataFilePath, String expected) throws IOException {
+ Path path = dataDir.resolve(oldPath);
+ Path lngFilePath = metaDir.resolve(metadataFilePath);
+ Files.createDirectories(lngFilePath.getParent());
+ Files.write(lngFilePath, expected.getBytes(UTF_8));
+
+ Optional migration = FilePathMigration.parse(vaultRoot, path);
+
+ Assertions.assertTrue(migration.isPresent());
+ Assertions.assertEquals(expected, migration.get().getOldCanonicalName());
+ }
+
+ }
+
+ @DisplayName("FilePathMigration.parse(...).get().migrate(...)")
+ @Nested
+ @TestInstance(TestInstance.Lifecycle.PER_CLASS)
+ class Migrating {
+
+ private FileSystem fs;
+ private Path vaultRoot;
+ private Path dataDir;
+ private Path metaDir;
+
+ @BeforeAll
+ public void beforeAll() {
+ fs = Jimfs.newFileSystem(Configuration.unix());
+ vaultRoot = fs.getPath("/vaultDir");
+ dataDir = vaultRoot.resolve("d");
+ metaDir = vaultRoot.resolve("m");
+ }
+
+ @BeforeEach
+ public void beforeEach() throws IOException {
+ Files.createDirectory(vaultRoot);
+ Files.createDirectory(dataDir);
+ Files.createDirectory(metaDir);
+ }
+
+ @AfterEach
+ public void afterEach() throws IOException {
+ MoreFiles.deleteRecursively(vaultRoot, RecursiveDeleteOption.ALLOW_INSECURE);
+ }
+
+ @DisplayName("migrate non-shortened files")
+ @ParameterizedTest(name = "migrating {0} to {1}")
+ @CsvSource({
+ "00/000000000000000000000000000000/ORSXG5A=,00/000000000000000000000000000000/dGVzdA==.c9r",
+ "00/000000000000000000000000000000/0ORSXG5A=,00/000000000000000000000000000000/dGVzdA==.c9r/dir.c9r",
+ "00/000000000000000000000000000000/1SORSXG5A=,00/000000000000000000000000000000/dGVzdA==.c9r/symlink.c9r",
+ "00/000000000000000000000000000000/conflict_ORSXG5A=,00/000000000000000000000000000000/dGVzdA==.c9r",
+ "00/000000000000000000000000000000/0ORSXG5A= (conflict),00/000000000000000000000000000000/dGVzdA==.c9r/dir.c9r",
+ "00/000000000000000000000000000000/conflict_1SORSXG5A= (conflict),00/000000000000000000000000000000/dGVzdA==.c9r/symlink.c9r",
+ })
+ public void testMigrateUnshortened(String oldPathStr, String expectedResult) throws IOException {
+ Path oldPath = dataDir.resolve(oldPathStr);
+ Files.createDirectories(oldPath.getParent());
+ Files.write(oldPath, "test".getBytes(UTF_8));
+
+ Path newPath = FilePathMigration.parse(vaultRoot, oldPath).get().migrate();
+
+ Assertions.assertEquals(dataDir.resolve(expectedResult), newPath);
+ Assertions.assertTrue(Files.exists(newPath));
+ Assertions.assertFalse(Files.exists(oldPath));
+ }
+
+ @DisplayName("migrate shortened files")
+ @ParameterizedTest(name = "migrating {0} to {3}")
+ @CsvSource({
+ "00/000000000000000000000000000000/NTJDZUB3J5S25LGO7CD4TE5VOJCSW7HF.lng,NT/JD/NTJDZUB3J5S25LGO7CD4TE5VOJCSW7HF.lng,ORSXG5A=,00/000000000000000000000000000000/dGVzdA==.c9r",
+ "00/000000000000000000000000000000/ZNPCXPWRWYFOGTZHVDBOOQDYPAMKKI5R.lng,ZN/PC/ZNPCXPWRWYFOGTZHVDBOOQDYPAMKKI5R.lng,0ORSXG5A=,00/000000000000000000000000000000/dGVzdA==.c9r/dir.c9r",
+ "00/000000000000000000000000000000/NUC3VFSMWKLD4526JDZKSE5V2IIMSYW5.lng,NU/C3/NUC3VFSMWKLD4526JDZKSE5V2IIMSYW5.lng,1SORSXG5A=,00/000000000000000000000000000000/dGVzdA==.c9r/symlink.c9r",
+ "00/000000000000000000000000000000/LPFZEP7JSREQMANHG7PRTOLSEKJM5JP5.lng,LP/FZ/LPFZEP7JSREQMANHG7PRTOLSEKJM5JP5.lng,ORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG===,00/000000000000000000000000000000/30xtS3YjsiMJRwu1oAVc_0S2aAU=.c9s/contents.c9r",
+ "00/000000000000000000000000000000/7LX7VYDWDWXRPL7ZKTTCVGUPMGPRNUSG.lng,7L/X7/7LX7VYDWDWXRPL7ZKTTCVGUPMGPRNUSG.lng,0ORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG===,00/000000000000000000000000000000/30xtS3YjsiMJRwu1oAVc_0S2aAU=.c9s/dir.c9r",
+ "00/000000000000000000000000000000/MGBBDEW456AMIDODOA3FUOQ3WNYNQNHZ.lng,MG/BB/MGBBDEW456AMIDODOA3FUOQ3WNYNQNHZ.lng,1SORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG===,00/000000000000000000000000000000/30xtS3YjsiMJRwu1oAVc_0S2aAU=.c9s/symlink.c9r",
+ "00/000000000000000000000000000000/conflict_NTJDZUB3J5S25LGO7CD4TE5VOJCSW7HF.lng,NT/JD/NTJDZUB3J5S25LGO7CD4TE5VOJCSW7HF.lng,ORSXG5A=,00/000000000000000000000000000000/dGVzdA==.c9r",
+ "00/000000000000000000000000000000/MGBBDEW456AMIDODOA3FUOQ3WNYNQNHZ (conflict).lng,MG/BB/MGBBDEW456AMIDODOA3FUOQ3WNYNQNHZ.lng,1SORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG===,00/000000000000000000000000000000/30xtS3YjsiMJRwu1oAVc_0S2aAU=.c9s/symlink.c9r",
+ })
+ public void testMigrateShortened(String oldPathStr, String metadataFilePath, String canonicalOldName, String expectedResult) throws IOException {
+ Path oldPath = dataDir.resolve(oldPathStr);
+ Files.createDirectories(oldPath.getParent());
+ Files.write(oldPath, "test".getBytes(UTF_8));
+ Path lngFilePath = metaDir.resolve(metadataFilePath);
+ Files.createDirectories(lngFilePath.getParent());
+ Files.write(lngFilePath, canonicalOldName.getBytes(UTF_8));
+
+ Path newPath = FilePathMigration.parse(vaultRoot, oldPath).get().migrate();
+
+ Assertions.assertEquals(dataDir.resolve(expectedResult), newPath);
+ Assertions.assertTrue(Files.exists(newPath));
+ Assertions.assertFalse(Files.exists(oldPath));
+ }
+
+
+ }
+
+}
diff --git a/src/test/java/org/cryptomator/cryptofs/migration/v7/Version7MigratorTest.java b/src/test/java/org/cryptomator/cryptofs/migration/v7/Version7MigratorTest.java
new file mode 100644
index 00000000..aa3e758d
--- /dev/null
+++ b/src/test/java/org/cryptomator/cryptofs/migration/v7/Version7MigratorTest.java
@@ -0,0 +1,117 @@
+package org.cryptomator.cryptofs.migration.v7;
+
+import com.google.common.jimfs.Configuration;
+import com.google.common.jimfs.Jimfs;
+import org.cryptomator.cryptofs.migration.api.Migrator;
+import org.cryptomator.cryptofs.mocks.NullSecureRandom;
+import org.cryptomator.cryptolib.Cryptors;
+import org.cryptomator.cryptolib.api.Cryptor;
+import org.cryptomator.cryptolib.api.CryptorProvider;
+import org.cryptomator.cryptolib.api.KeyFile;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public class Version7MigratorTest {
+
+ private FileSystem fs;
+ private Path vaultRoot;
+ private Path dataDir;
+ private Path metaDir;
+ private Path masterkeyFile;
+ private CryptorProvider cryptorProvider;
+
+ @BeforeEach
+ public void setup() throws IOException {
+ cryptorProvider = Cryptors.version1(NullSecureRandom.INSTANCE);
+ fs = Jimfs.newFileSystem(Configuration.unix());
+ vaultRoot = fs.getPath("/vaultDir");
+ dataDir = vaultRoot.resolve("d");
+ metaDir = vaultRoot.resolve("m");
+ masterkeyFile = vaultRoot.resolve("masterkey.cryptomator");
+ Files.createDirectory(vaultRoot);
+ Files.createDirectory(dataDir);
+ Files.createDirectory(metaDir);
+ try (Cryptor cryptor = cryptorProvider.createNew()) {
+ KeyFile keyFile = cryptor.writeKeysToMasterkeyFile("test", 6);
+ Files.write(masterkeyFile, keyFile.serialize());
+ }
+ }
+
+ @AfterEach
+ public void teardown() throws IOException {
+ fs.close();
+ }
+
+ @Test
+ public void testKeyfileGetsUpdates() throws IOException {
+ KeyFile beforeMigration = KeyFile.parse(Files.readAllBytes(masterkeyFile));
+ Assertions.assertEquals(6, beforeMigration.getVersion());
+
+ Migrator migrator = new Version7Migrator(cryptorProvider);
+ migrator.migrate(vaultRoot, "masterkey.cryptomator", "test");
+
+ KeyFile afterMigration = KeyFile.parse(Files.readAllBytes(masterkeyFile));
+ Assertions.assertEquals(7, afterMigration.getVersion());
+ }
+
+ @Test
+ public void testMDirectoryGetsDeleted() throws IOException {
+ Migrator migrator = new Version7Migrator(cryptorProvider);
+ migrator.migrate(vaultRoot, "masterkey.cryptomator", "test");
+
+ Assertions.assertFalse(Files.exists(metaDir));
+ }
+
+ @Test
+ public void testMigrationOfNormalFile() throws IOException {
+ Path dir = dataDir.resolve("AA/BBBBBCCCCCDDDDDEEEEEFFFFFGGGGG");
+ Files.createDirectories(dir);
+ Path fileBeforeMigration = dir.resolve("MZUWYZLOMFWWK===");
+ Path fileAfterMigration = dir.resolve("ZmlsZW5hbWU=.c9r");
+ Files.createFile(fileBeforeMigration);
+
+ Migrator migrator = new Version7Migrator(cryptorProvider);
+ migrator.migrate(vaultRoot, "masterkey.cryptomator", "test");
+
+ Assertions.assertFalse(Files.exists(fileBeforeMigration));
+ Assertions.assertTrue(Files.exists(fileAfterMigration));
+ }
+
+ @Test
+ public void testMigrationOfNormalDirectory() throws IOException {
+ Path dir = dataDir.resolve("AA/BBBBBCCCCCDDDDDEEEEEFFFFFGGGGG");
+ Files.createDirectories(dir);
+ Path fileBeforeMigration = dir.resolve("0MZUWYZLOMFWWK===");
+ Path fileAfterMigration = dir.resolve("ZmlsZW5hbWU=.c9r/dir.c9r");
+ Files.createFile(fileBeforeMigration);
+
+ Migrator migrator = new Version7Migrator(cryptorProvider);
+ migrator.migrate(vaultRoot, "masterkey.cryptomator", "test");
+
+ Assertions.assertFalse(Files.exists(fileBeforeMigration));
+ Assertions.assertTrue(Files.exists(fileAfterMigration));
+ }
+
+ @Test
+ public void testMigrationOfNormalSymlink() throws IOException {
+ Path dir = dataDir.resolve("AA/BBBBBCCCCCDDDDDEEEEEFFFFFGGGGG");
+ Files.createDirectories(dir);
+ Path fileBeforeMigration = dir.resolve("1SMZUWYZLOMFWWK===");
+ Path fileAfterMigration = dir.resolve("ZmlsZW5hbWU=.c9r/symlink.c9r");
+ Files.createFile(fileBeforeMigration);
+
+ Migrator migrator = new Version7Migrator(cryptorProvider);
+ migrator.migrate(vaultRoot, "masterkey.cryptomator", "test");
+
+ Assertions.assertFalse(Files.exists(fileBeforeMigration));
+ Assertions.assertTrue(Files.exists(fileAfterMigration));
+ }
+
+}