diff --git a/pom.xml b/pom.xml
index 2ee04425..18399081 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2,7 +2,7 @@
4.0.0
org.cryptomator
cryptofs
- 2.4.2
+ 2.4.3
Cryptomator Crypto Filesystem
This library provides the Java filesystem provider used by Cryptomator.
https://github.com/cryptomator/cryptofs
@@ -18,21 +18,21 @@
17
- 2.0.3
- 3.19.1
- 2.41
+ 2.1.0-rc1
+ 4.0.0
+ 2.44
31.1-jre
- 1.7.36
+ 2.0.3
- 5.8.2
- 4.4.0
+ 5.9.1
+ 4.8.0
2.2
- 7.0.3
- 0.8.7
- 1.6.12
+ 7.2.1
+ 0.8.8
+ 1.6.13
@@ -71,6 +71,12 @@
java-jwt
${jwt.version}
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.14.0-rc1
+
com.google.dagger
dagger
@@ -96,7 +102,7 @@
org.mockito
- mockito-core
+ mockito-inline
${mockito.version}
test
@@ -125,7 +131,7 @@
org.apache.maven.plugins
maven-compiler-plugin
- 3.8.1
+ 3.10.1
true
@@ -140,19 +146,17 @@
org.apache.maven.plugins
maven-surefire-plugin
- 3.0.0-M5
+ 3.0.0-M7
ERROR
-
- false
org.apache.maven.plugins
maven-jar-plugin
- 3.2.0
+ 3.3.0
maven-source-plugin
@@ -168,7 +172,7 @@
maven-javadoc-plugin
- 3.3.0
+ 3.4.1
attach-javadocs
diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java
index bf99e5b4..08d33a11 100644
--- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java
+++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java
@@ -378,7 +378,7 @@ private FileChannel newFileChannelFromFile(CryptoPath cleartextFilePath, Effecti
Files.createDirectories(ciphertextPath.getRawPath()); // suppresses FileAlreadyExists
}
- FileChannel ch = openCryptoFiles.getOrCreate(ciphertextFilePath).newFileChannel(options); // might throw FileAlreadyExists
+ FileChannel ch = openCryptoFiles.getOrCreate(ciphertextFilePath).newFileChannel(options, attrs); // might throw FileAlreadyExists
try {
if (options.writable()) {
ciphertextPath.persistLongFileName();
diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java
index 18978cd3..fb79a344 100644
--- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java
+++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java
@@ -44,6 +44,15 @@ public class CryptoFileSystemProperties extends AbstractMap {
static final int DEFAULT_MAX_CLEARTEXT_NAME_LENGTH = LongFileNameProvider.MAX_FILENAME_BUFFER_SIZE;
+ /**
+ * Shortening threshold for ciphertext filenames.
+ *
+ * @since 2.5.0
+ */
+ public static final String PROPERTY_SHORTENING_THRESHOLD = "shorteningThreshold";
+
+ static final int DEFAULT_SHORTENING_THRESHOLD = 220;
+
/**
* Key identifying the key loader used during initialization.
*
@@ -105,6 +114,7 @@ private CryptoFileSystemProperties(Builder builder) {
Map.entry(PROPERTY_VAULTCONFIG_FILENAME, builder.vaultConfigFilename), //
Map.entry(PROPERTY_MASTERKEY_FILENAME, builder.masterkeyFilename), //
Map.entry(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, builder.maxCleartextNameLength), //
+ Map.entry(PROPERTY_SHORTENING_THRESHOLD, builder.shorteningThreshold), //
Map.entry(PROPERTY_CIPHER_COMBO, builder.cipherCombo) //
);
}
@@ -139,6 +149,10 @@ int maxCleartextNameLength() {
return (int) get(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH);
}
+ int shorteningThreshold() {
+ return (int) get(PROPERTY_SHORTENING_THRESHOLD);
+ }
+
@Override
public Set> entrySet() {
return entries;
@@ -193,6 +207,7 @@ public static class Builder {
private String vaultConfigFilename = DEFAULT_VAULTCONFIG_FILENAME;
private String masterkeyFilename = DEFAULT_MASTERKEY_FILENAME;
private int maxCleartextNameLength = DEFAULT_MAX_CLEARTEXT_NAME_LENGTH;
+ private int shorteningThreshold = DEFAULT_SHORTENING_THRESHOLD;
private Builder() {
}
@@ -203,6 +218,7 @@ private Builder(Map properties) {
checkedSet(String.class, PROPERTY_MASTERKEY_FILENAME, properties, this::withMasterkeyFilename);
checkedSet(Set.class, PROPERTY_FILESYSTEM_FLAGS, properties, this::withFlags);
checkedSet(Integer.class, PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, properties, this::withMaxCleartextNameLength);
+ checkedSet(Integer.class, PROPERTY_SHORTENING_THRESHOLD, properties, this::withShorteningThreshold);
checkedSet(CryptorProvider.Scheme.class, PROPERTY_CIPHER_COMBO, properties, this::withCipherCombo);
}
@@ -231,6 +247,18 @@ public Builder withMaxCleartextNameLength(int maxCleartextNameLength) {
return this;
}
+ /**
+ * Sets the shortening threshold used during vault initialization.
+ *
+ * @param shorteningThreshold The maximum ciphertext filename length not to be shortened
+ * @return this
+ * @since 2.5.0
+ */
+ public Builder withShorteningThreshold(int shorteningThreshold) {
+ this.shorteningThreshold = shorteningThreshold;
+ return this;
+ }
+
/**
* Sets the cipher combo used during vault initialization.
diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java
index 6a8c10fd..9de7e051 100644
--- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java
+++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java
@@ -142,7 +142,7 @@ public static void initialize(Path pathToVault, CryptoFileSystemProperties prope
throw new NotDirectoryException(pathToVault.toString());
}
byte[] rawKey = new byte[0];
- var config = VaultConfig.createNew().cipherCombo(properties.cipherCombo()).shorteningThreshold(Constants.DEFAULT_SHORTENING_THRESHOLD).build();
+ var config = VaultConfig.createNew().cipherCombo(properties.cipherCombo()).shorteningThreshold(properties.shorteningThreshold()).build();
try (Masterkey key = properties.keyLoader().loadKey(keyId);
Cryptor cryptor = CryptorProvider.forScheme(config.getCipherCombo()).provide(key, strongSecureRandom())) {
rawKey = key.getEncoded();
diff --git a/src/main/java/org/cryptomator/cryptofs/LongFileNameProvider.java b/src/main/java/org/cryptomator/cryptofs/LongFileNameProvider.java
index 17b183b6..94d32b68 100644
--- a/src/main/java/org/cryptomator/cryptofs/LongFileNameProvider.java
+++ b/src/main/java/org/cryptomator/cryptofs/LongFileNameProvider.java
@@ -82,11 +82,13 @@ public String inflate(Path c9sPath) throws IOException {
public DeflatedFileName deflate(Path c9rPath) {
String longFileName = c9rPath.getFileName().toString();
byte[] longFileNameBytes = longFileName.getBytes(UTF_8);
- byte[] hash = MessageDigestSupplier.SHA1.get().digest(longFileNameBytes);
- String shortName = BASE64.encode(hash) + DEFLATED_FILE_SUFFIX;
- Path c9sPath = c9rPath.resolveSibling(shortName);
- longNames.put(c9sPath, longFileName);
- return new DeflatedFileName(c9sPath, longFileName, readonlyFlag);
+ try (var sha1 = MessageDigestSupplier.SHA1.instance()) {
+ byte[] hash = sha1.get().digest(longFileNameBytes);
+ String shortName = BASE64.encode(hash) + DEFLATED_FILE_SUFFIX;
+ Path c9sPath = c9rPath.resolveSibling(shortName);
+ longNames.put(c9sPath, longFileName);
+ return new DeflatedFileName(c9sPath, longFileName, readonlyFlag);
+ }
}
public static class DeflatedFileName {
diff --git a/src/main/java/org/cryptomator/cryptofs/attr/CryptoBasicFileAttributes.java b/src/main/java/org/cryptomator/cryptofs/attr/CryptoBasicFileAttributes.java
index f1aae4f2..7bdacd3c 100644
--- a/src/main/java/org/cryptomator/cryptofs/attr/CryptoBasicFileAttributes.java
+++ b/src/main/java/org/cryptomator/cryptofs/attr/CryptoBasicFileAttributes.java
@@ -35,8 +35,8 @@ sealed class CryptoBasicFileAttributes implements BasicFileAttributes
public CryptoBasicFileAttributes(BasicFileAttributes delegate, CiphertextFileType ciphertextFileType, Path ciphertextPath, Cryptor cryptor, Optional openCryptoFile) {
this.ciphertextFileType = ciphertextFileType;
this.size = switch (ciphertextFileType) {
- case SYMLINK, DIRECTORY -> delegate.size();
- case FILE -> getPlaintextFileSize(ciphertextPath, delegate.size(), openCryptoFile, cryptor);
+ case DIRECTORY -> delegate.size();
+ case SYMLINK, FILE -> getPlaintextFileSize(ciphertextPath, delegate.size(), openCryptoFile, cryptor);
};
this.lastModifiedTime = openCryptoFile.map(OpenCryptoFile::getLastModifiedTime).orElseGet(delegate::lastModifiedTime);
this.lastAccessTime = openCryptoFile.map(openFile -> FileTime.from(Instant.now())).orElseGet(delegate::lastAccessTime);
diff --git a/src/main/java/org/cryptomator/cryptofs/common/Constants.java b/src/main/java/org/cryptomator/cryptofs/common/Constants.java
index 7ad182cf..fe05baa5 100644
--- a/src/main/java/org/cryptomator/cryptofs/common/Constants.java
+++ b/src/main/java/org/cryptomator/cryptofs/common/Constants.java
@@ -26,7 +26,6 @@ private Constants() {
public static final String CONTENTS_FILE_NAME = "contents.c9r";
public static final String INFLATED_FILE_NAME = "name.c9s";
- public static final int DEFAULT_SHORTENING_THRESHOLD = 220;
public static final int MAX_SYMLINK_LENGTH = 32767; // max path length on NTFS and FAT32: 32k-1
public static final int MAX_DIR_FILE_LENGTH = 36; // UUIDv4: hex-encoded 16 byte int + 4 hyphens = 36 ASCII chars
public static final int MIN_CIPHER_NAME_LENGTH = 26; //rounded up base64url encoded (16 bytes IV + 0 bytes empty string) + file suffix = 26 ASCII chars
diff --git a/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFile.java b/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFile.java
index 98b22756..b41e35fe 100644
--- a/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFile.java
+++ b/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFile.java
@@ -21,6 +21,7 @@
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
+import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileTime;
import java.time.Instant;
import java.util.Optional;
@@ -65,7 +66,7 @@ public OpenCryptoFile(FileCloseListener listener, ChunkCache chunkCache, Cryptor
* @return A new file channel. Ideally used in a try-with-resource statement. If the channel is not properly closed, this OpenCryptoFile will stay open indefinite.
* @throws IOException
*/
- public synchronized FileChannel newFileChannel(EffectiveOpenOptions options) throws IOException {
+ public synchronized FileChannel newFileChannel(EffectiveOpenOptions options, FileAttribute>... attrs) throws IOException {
Path path = currentFilePath.get();
if (options.truncateExisting()) {
@@ -75,7 +76,7 @@ public synchronized FileChannel newFileChannel(EffectiveOpenOptions options) thr
FileChannel ciphertextFileChannel = null;
CleartextFileChannel cleartextFileChannel = null;
try {
- ciphertextFileChannel = path.getFileSystem().provider().newFileChannel(path, options.createOpenOptionsForEncryptedFile());
+ ciphertextFileChannel = path.getFileSystem().provider().newFileChannel(path, options.createOpenOptionsForEncryptedFile(), attrs);
final FileHeader header;
final boolean isNewHeader;
if (ciphertextFileChannel.size() == 0l) {
diff --git a/src/main/java/org/cryptomator/cryptofs/health/dirid/DirIdCheck.java b/src/main/java/org/cryptomator/cryptofs/health/dirid/DirIdCheck.java
index 4cc53321..b8bd6791 100644
--- a/src/main/java/org/cryptomator/cryptofs/health/dirid/DirIdCheck.java
+++ b/src/main/java/org/cryptomator/cryptofs/health/dirid/DirIdCheck.java
@@ -61,18 +61,22 @@ public void check(Path pathToVault, VaultConfig config, Masterkey masterkey, Cry
boolean foundDir = dirVisitor.secondLevelDirs.remove(expectedDir);
if (foundDir) {
iter.remove();
- resultCollector.accept(new HealthyDir(dirId, dirIdFile, expectedDir));
+ if (Files.exists(expectedDir.resolve(Constants.DIR_ID_FILE))) {
+ resultCollector.accept(new HealthyDir(dirId, dirIdFile, expectedDir));
+ } else {
+ resultCollector.accept(new MissingDirIdBackup(dirId, expectedDir));
+ }
}
}
// remaining dirIds (i.e. missing dirs):
dirVisitor.dirIds.forEach((dirId, dirIdFile) -> {
- resultCollector.accept(new MissingDirectory(dirId, dirIdFile));
+ resultCollector.accept(new MissingContentDir(dirId, dirIdFile));
});
// remaining folders (i.e. missing dir.c9r files):
dirVisitor.secondLevelDirs.forEach(dir -> {
- resultCollector.accept(new OrphanDir(dir));
+ resultCollector.accept(new OrphanContentDir(dir));
});
}
@@ -83,6 +87,8 @@ static class DirVisitor extends SimpleFileVisitor {
private final Consumer resultCollector;
public final Map dirIds = new HashMap<>(); // contents of all found dir.c9r files
public final Set secondLevelDirs = new HashSet<>(); // all d/2/30 dirs
+ public final Set c9rDirsWithDirId = new HashSet<>(); // all d/2/30/abcd=.c9r dirs containing a dirId file
+
public DirVisitor(Path dataDirPath, Consumer resultCollector) {
this.dataDirPath = dataDirPath;
@@ -93,6 +99,7 @@ public DirVisitor(Path dataDirPath, Consumer resultCollector)
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (Constants.DIR_FILE_NAME.equals(file.getFileName().toString())) {
+ c9rDirsWithDirId.add(file.getParent());
return visitDirFile(file, attrs);
}
return FileVisitResult.CONTINUE;
@@ -100,12 +107,20 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
private FileVisitResult visitDirFile(Path file, BasicFileAttributes attrs) throws IOException {
assert Constants.DIR_FILE_NAME.equals(file.getFileName().toString());
+ var parentDirName = file.getParent().getFileName().toString();
+
+ if (!(parentDirName.endsWith(Constants.CRYPTOMATOR_FILE_SUFFIX) || parentDirName.endsWith(Constants.DEFLATED_FILE_SUFFIX))) {
+ LOG.warn("Encountered loose dir.c9r file.");
+ resultCollector.accept(new LooseDirIdFile(file));
+ return FileVisitResult.CONTINUE;
+ }
+
if (attrs.size() > Constants.MAX_DIR_FILE_LENGTH) {
LOG.warn("Encountered dir.c9r file of size {}", attrs.size());
- resultCollector.accept(new ObeseDirFile(file, attrs.size()));
+ resultCollector.accept(new ObeseDirIdFile(file, attrs.size()));
} else if (attrs.size() == 0) {
LOG.warn("Empty dir.c9r file at {}.", file);
- resultCollector.accept(new EmptyDirFile(file));
+ resultCollector.accept(new EmptyDirIdFile(file));
} else {
byte[] bytes = Files.readAllBytes(file);
String dirId = new String(bytes, StandardCharsets.UTF_8);
@@ -115,6 +130,7 @@ private FileVisitResult visitDirFile(Path file, BasicFileAttributes attrs) throw
resultCollector.accept(new DirIdCollision(dirId, file, otherFile));
} else {
dirIds.put(dirId, file);
+ c9rDirsWithDirId.add(file);
}
}
return FileVisitResult.SKIP_SIBLINGS;
@@ -128,6 +144,16 @@ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
}
return FileVisitResult.CONTINUE;
}
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir, IOException e) {
+ var dirName = dir.getFileName().toString();
+ if (dirName.endsWith(Constants.CRYPTOMATOR_FILE_SUFFIX) && !c9rDirsWithDirId.contains(dir)) {
+ LOG.warn("Missing dirId file for c9r directory {}.", dir);
+ resultCollector.accept(new MissingDirIdFile(dir));
+ }
+ return FileVisitResult.CONTINUE;
+ }
}
}
diff --git a/src/main/java/org/cryptomator/cryptofs/health/dirid/EmptyDirFile.java b/src/main/java/org/cryptomator/cryptofs/health/dirid/EmptyDirIdFile.java
similarity index 84%
rename from src/main/java/org/cryptomator/cryptofs/health/dirid/EmptyDirFile.java
rename to src/main/java/org/cryptomator/cryptofs/health/dirid/EmptyDirIdFile.java
index 551ed5f2..37401645 100644
--- a/src/main/java/org/cryptomator/cryptofs/health/dirid/EmptyDirFile.java
+++ b/src/main/java/org/cryptomator/cryptofs/health/dirid/EmptyDirIdFile.java
@@ -16,11 +16,11 @@
*
* @see org.cryptomator.cryptofs.common.Constants#ROOT_DIR_ID
*/
-public class EmptyDirFile implements DiagnosticResult {
+public class EmptyDirIdFile implements DiagnosticResult {
- final Path dirFile;
+ final Path dirIdFile;
- public EmptyDirFile(Path dirFile) {this.dirFile = dirFile;}
+ public EmptyDirIdFile(Path dirIdFile) {this.dirIdFile = dirIdFile;}
@Override
public Severity getSeverity() {
@@ -29,7 +29,7 @@ public Severity getSeverity() {
@Override
public String toString() {
- return String.format("File %s is empty, expected content", dirFile);
+ return String.format("File %s is empty, expected content", dirIdFile);
}
/*
@@ -44,6 +44,6 @@ public void fix(Path pathToVault, VaultConfig config, Masterkey masterkey, Crypt
@Override
public Map details() {
- return Map.of(DIR_ID_FILE, dirFile.toString());
+ return Map.of(DIR_ID_FILE, dirIdFile.toString());
}
}
diff --git a/src/main/java/org/cryptomator/cryptofs/health/dirid/LooseDirIdFile.java b/src/main/java/org/cryptomator/cryptofs/health/dirid/LooseDirIdFile.java
new file mode 100644
index 00000000..eda23267
--- /dev/null
+++ b/src/main/java/org/cryptomator/cryptofs/health/dirid/LooseDirIdFile.java
@@ -0,0 +1,42 @@
+package org.cryptomator.cryptofs.health.dirid;
+
+import org.cryptomator.cryptofs.VaultConfig;
+import org.cryptomator.cryptofs.health.api.DiagnosticResult;
+import org.cryptomator.cryptolib.api.Cryptor;
+import org.cryptomator.cryptolib.api.Masterkey;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+
+import static org.cryptomator.cryptofs.health.api.CommonDetailKeys.DIR_ID_FILE;
+
+public class LooseDirIdFile implements DiagnosticResult {
+
+ final Path dirIdFile;
+
+ LooseDirIdFile(Path dirIdFile) {
+ this.dirIdFile = dirIdFile;
+ }
+
+ @Override
+ public Severity getSeverity() {
+ return Severity.INFO;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("A dir.c9r without proper parent found: (%s). .", dirIdFile);
+ }
+
+ @Override
+ public void fix(Path pathToVault, VaultConfig config, Masterkey masterkey, Cryptor cryptor) throws IOException {
+ Files.deleteIfExists(dirIdFile);
+ }
+
+ @Override
+ public Map details() {
+ return Map.of(DIR_ID_FILE, dirIdFile.toString());
+ }
+}
diff --git a/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingContentDir.java b/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingContentDir.java
new file mode 100644
index 00000000..db519612
--- /dev/null
+++ b/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingContentDir.java
@@ -0,0 +1,60 @@
+package org.cryptomator.cryptofs.health.dirid;
+
+import org.cryptomator.cryptofs.CryptoPathMapper;
+import org.cryptomator.cryptofs.DirectoryIdBackup;
+import org.cryptomator.cryptofs.VaultConfig;
+import org.cryptomator.cryptofs.common.Constants;
+import org.cryptomator.cryptofs.health.api.DiagnosticResult;
+import org.cryptomator.cryptolib.api.Cryptor;
+import org.cryptomator.cryptolib.api.Masterkey;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+
+import static org.cryptomator.cryptofs.health.api.CommonDetailKeys.DIR_ID;
+import static org.cryptomator.cryptofs.health.api.CommonDetailKeys.DIR_ID_FILE;
+
+/**
+ * Valid dir.c9r file, nonexisting content dir
+ */
+public class MissingContentDir implements DiagnosticResult {
+
+ final String dirId;
+ final Path dirIdFile;
+
+ MissingContentDir(String dirId, Path dirIdFile) {
+ this.dirId = dirId;
+ this.dirIdFile = dirIdFile;
+ }
+
+ @Override
+ public Severity getSeverity() {
+ return Severity.WARN;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("dir.c9r file (%s) points to non-existing directory.", dirIdFile);
+ }
+
+ @Override
+ public Map details() {
+ return Map.of(DIR_ID, dirId, //
+ DIR_ID_FILE, dirIdFile.toString());
+ }
+
+ @Override
+ public void fix(Path pathToVault, VaultConfig config, Masterkey masterkey, Cryptor cryptor) throws IOException {
+ var dirIdHash = cryptor.fileNameCryptor().hashDirectoryId(dirId);
+ Path dirPath = pathToVault.resolve(Constants.DATA_DIR_NAME).resolve(dirIdHash.substring(0, 2)).resolve(dirIdHash.substring(2, 30));
+ Files.createDirectories(dirPath);
+ createDirIdBackupFile(cryptor, new CryptoPathMapper.CiphertextDirectory(dirId, dirPath));
+ }
+
+ //visible for testing
+ void createDirIdBackupFile(Cryptor cryptor, CryptoPathMapper.CiphertextDirectory cipherDirObj) throws IOException {
+ new DirectoryIdBackup(cryptor).execute(cipherDirObj);
+ }
+}
diff --git a/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackup.java b/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackup.java
new file mode 100644
index 00000000..e3ee0c0b
--- /dev/null
+++ b/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackup.java
@@ -0,0 +1,33 @@
+package org.cryptomator.cryptofs.health.dirid;
+
+import org.cryptomator.cryptofs.CryptoPathMapper;
+import org.cryptomator.cryptofs.DirectoryIdBackup;
+import org.cryptomator.cryptofs.VaultConfig;
+import org.cryptomator.cryptofs.health.api.DiagnosticResult;
+import org.cryptomator.cryptolib.api.Cryptor;
+import org.cryptomator.cryptolib.api.Masterkey;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+/**
+ * The dir id backup file {@value org.cryptomator.cryptofs.common.Constants#DIR_ID_FILE} is missing.
+ */
+public record MissingDirIdBackup(String dirId, Path cipherDir) implements DiagnosticResult {
+
+ @Override
+ public Severity getSeverity() {
+ return Severity.WARN;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Directory ID backup for directory %s is missing.", cipherDir);
+ }
+
+ @Override
+ public void fix(Path pathToVault, VaultConfig config, Masterkey masterkey, Cryptor cryptor) throws IOException {
+ DirectoryIdBackup dirIdBackup = new DirectoryIdBackup(cryptor);
+ dirIdBackup.execute(new CryptoPathMapper.CiphertextDirectory(dirId, cipherDir));
+ }
+}
diff --git a/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdFile.java b/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdFile.java
new file mode 100644
index 00000000..766b5e89
--- /dev/null
+++ b/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdFile.java
@@ -0,0 +1,45 @@
+package org.cryptomator.cryptofs.health.dirid;
+
+import org.cryptomator.cryptofs.VaultConfig;
+import org.cryptomator.cryptofs.health.api.CommonDetailKeys;
+import org.cryptomator.cryptofs.health.api.DiagnosticResult;
+import org.cryptomator.cryptolib.api.Cryptor;
+import org.cryptomator.cryptolib.api.Masterkey;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+
+/**
+ * A c9r directory without a dirId file.
+ */
+public class MissingDirIdFile implements DiagnosticResult {
+
+ final Path c9rDirectory;
+
+ public MissingDirIdFile(Path c9rDirectory) {
+ this.c9rDirectory = c9rDirectory;
+ }
+
+ @Override
+ public Severity getSeverity() {
+ return Severity.WARN;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Directory to contain dir.c9r exists (%s), but dir.c9r file is missing.", c9rDirectory);
+ }
+
+ @Override
+ public void fix(Path pathToVault, VaultConfig config, Masterkey masterkey, Cryptor cryptor) throws IOException {
+ Files.deleteIfExists(c9rDirectory);
+ }
+
+ @Override
+ public Map details() {
+ return Map.of(CommonDetailKeys.ENCRYPTED_PATH, c9rDirectory.toString());
+ }
+
+}
diff --git a/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingDirectory.java b/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingDirectory.java
deleted file mode 100644
index 548275ab..00000000
--- a/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingDirectory.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package org.cryptomator.cryptofs.health.dirid;
-
-import org.cryptomator.cryptofs.health.api.DiagnosticResult;
-
-import java.nio.file.Path;
-import java.util.Map;
-
-import static org.cryptomator.cryptofs.health.api.CommonDetailKeys.DIR_ID;
-import static org.cryptomator.cryptofs.health.api.CommonDetailKeys.DIR_ID_FILE;
-
-/**
- * Valid dir.c9r file, nonexisting dir
- */
-public class MissingDirectory implements DiagnosticResult {
-
- //TODO: maybe add not-existing dir path
- final String dirId;
- final Path file;
-
- MissingDirectory(String dirId, Path file) {
- this.dirId = dirId;
- this.file = file;
- }
-
- @Override
- public Severity getSeverity() {
- return Severity.CRITICAL;
- }
-
- @Override
- public String toString() {
- return String.format("dir.c9r file (%s) points to non-existing directory.", file);
- }
-
- @Override
- public Map details() {
- return Map.of(DIR_ID, dirId, //
- DIR_ID_FILE, file.toString());
- }
- // fix: create dir?
-}
diff --git a/src/main/java/org/cryptomator/cryptofs/health/dirid/ObeseDirFile.java b/src/main/java/org/cryptomator/cryptofs/health/dirid/ObeseDirIdFile.java
similarity index 72%
rename from src/main/java/org/cryptomator/cryptofs/health/dirid/ObeseDirFile.java
rename to src/main/java/org/cryptomator/cryptofs/health/dirid/ObeseDirIdFile.java
index 45627d7a..c45a9ce0 100644
--- a/src/main/java/org/cryptomator/cryptofs/health/dirid/ObeseDirFile.java
+++ b/src/main/java/org/cryptomator/cryptofs/health/dirid/ObeseDirIdFile.java
@@ -11,13 +11,13 @@
/**
* The dir.c9r file's size is too large.
*/
-public class ObeseDirFile implements DiagnosticResult {
+public class ObeseDirIdFile implements DiagnosticResult {
- final Path dirFile;
+ final Path dirIdFile;
final long size;
- ObeseDirFile(Path dirFile, long size) {
- this.dirFile = dirFile;
+ ObeseDirIdFile(Path dirIdFile, long size) {
+ this.dirIdFile = dirIdFile;
this.size = size;
}
@@ -28,12 +28,12 @@ public Severity getSeverity() {
@Override
public String toString() {
- return String.format("Unexpected file size of %s: %d should be ≤ %d", dirFile, size, Constants.MAX_DIR_FILE_LENGTH);
+ return String.format("Unexpected file size of %s: %d should be ≤ %d", dirIdFile, size, Constants.MAX_DIR_FILE_LENGTH);
}
@Override
public Map details() {
- return Map.of(DIR_ID_FILE, dirFile.toString(), //
+ return Map.of(DIR_ID_FILE, dirIdFile.toString(), //
"Size", Long.toString(size));
}
// potential fix: assign new dir id, move target dir
diff --git a/src/main/java/org/cryptomator/cryptofs/health/dirid/OrphanDir.java b/src/main/java/org/cryptomator/cryptofs/health/dirid/OrphanContentDir.java
similarity index 96%
rename from src/main/java/org/cryptomator/cryptofs/health/dirid/OrphanDir.java
rename to src/main/java/org/cryptomator/cryptofs/health/dirid/OrphanContentDir.java
index 3737d5b9..b0c226ca 100644
--- a/src/main/java/org/cryptomator/cryptofs/health/dirid/OrphanDir.java
+++ b/src/main/java/org/cryptomator/cryptofs/health/dirid/OrphanContentDir.java
@@ -37,19 +37,19 @@
/**
* An orphan directory is a detached node, not referenced by any dir.c9r file.
*/
-public class OrphanDir implements DiagnosticResult {
+public class OrphanContentDir implements DiagnosticResult {
- private static final Logger LOG = LoggerFactory.getLogger(OrphanDir.class);
+ private static final Logger LOG = LoggerFactory.getLogger(OrphanContentDir.class);
private static final String FILE_PREFIX = "file";
private static final String DIR_PREFIX = "directory";
private static final String SYMLINK_PREFIX = "symlink";
private static final String LONG_NAME_SUFFIX_BASE = "_withVeryLongName";
- final Path dir;
+ final Path contentDir;
- OrphanDir(Path dir) {
- this.dir = dir;
+ OrphanContentDir(Path contentDir) {
+ this.contentDir = contentDir;
}
@Override
@@ -59,12 +59,12 @@ public Severity getSeverity() {
@Override
public String toString() {
- return String.format("Orphan directory: %s", dir);
+ return String.format("Orphan directory: %s", contentDir);
}
@Override
public Map details() {
- return Map.of(ENCRYPTED_PATH, dir.toString());
+ return Map.of(ENCRYPTED_PATH, contentDir.toString());
}
@Override
@@ -72,8 +72,8 @@ public void fix(Path pathToVault, VaultConfig config, Masterkey masterkey, Crypt
var sha1 = getSha1MessageDigest();
String runId = Integer.toString((short) UUID.randomUUID().getMostSignificantBits(), 32);
Path dataDir = pathToVault.resolve(Constants.DATA_DIR_NAME);
- Path orphanedDir = dataDir.resolve(this.dir);
- String orphanDirIdHash = dir.getParent().getFileName().toString() + dir.getFileName().toString();
+ Path orphanedDir = dataDir.resolve(this.contentDir);
+ String orphanDirIdHash = contentDir.getParent().getFileName().toString() + contentDir.getFileName().toString();
Path recoveryDir = prepareRecoveryDir(pathToVault, cryptor.fileNameCryptor());
if (recoveryDir.toAbsolutePath().equals(orphanedDir.toAbsolutePath())) {
diff --git a/src/main/java/org/cryptomator/cryptofs/health/shortened/ShortenedNamesCheck.java b/src/main/java/org/cryptomator/cryptofs/health/shortened/ShortenedNamesCheck.java
index bc252716..af587671 100644
--- a/src/main/java/org/cryptomator/cryptofs/health/shortened/ShortenedNamesCheck.java
+++ b/src/main/java/org/cryptomator/cryptofs/health/shortened/ShortenedNamesCheck.java
@@ -148,8 +148,10 @@ enum SyntaxResult {
//visible for testing
String deflate(String longFileName) {
byte[] longFileNameBytes = longFileName.getBytes(UTF_8);
- byte[] hash = MessageDigestSupplier.SHA1.get().digest(longFileNameBytes);
- return BASE64URL.encode(hash) + DEFLATED_FILE_SUFFIX;
+ try (var sha1 = MessageDigestSupplier.SHA1.instance()) {
+ byte[] hash = sha1.get().digest(longFileNameBytes);
+ return BASE64URL.encode(hash) + DEFLATED_FILE_SUFFIX;
+ }
}
}
diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v7/FilePathMigration.java b/src/main/java/org/cryptomator/cryptofs/migration/v7/FilePathMigration.java
index 158efcf3..59852e3d 100644
--- a/src/main/java/org/cryptomator/cryptofs/migration/v7/FilePathMigration.java
+++ b/src/main/java/org/cryptomator/cryptofs/migration/v7/FilePathMigration.java
@@ -232,8 +232,10 @@ String getNewDeflatedName() throws InvalidOldFilenameException {
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;
+ try (var sha1 = MessageDigestSupplier.SHA1.instance()) {
+ byte[] hash = sha1.get().digest(longFileNameBytes);
+ return BASE64.encode(hash) + NEW_SHORTENED_SUFFIX;
+ }
} else {
return inflatedName;
}
diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java
index b994a2e4..5b0b1521 100644
--- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java
@@ -54,6 +54,7 @@
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFileAttributes;
import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
import java.nio.file.attribute.UserPrincipal;
import java.nio.file.spi.FileSystemProvider;
import java.util.Arrays;
@@ -366,7 +367,7 @@ public void setup() throws IOException {
when(ciphertextPath.getFilePath()).thenReturn(ciphertextFilePath);
when(openCryptoFiles.getOrCreate(ciphertextFilePath)).thenReturn(openCryptoFile);
when(ciphertextFilePath.getName(3)).thenReturn(mock(CryptoPath.class, "path.c9r"));
- when(openCryptoFile.newFileChannel(any())).thenReturn(fileChannel);
+ when(openCryptoFile.newFileChannel(any(), any())).thenReturn(fileChannel);
}
@Nested
@@ -411,6 +412,18 @@ public void testNewFileChannelCreate2() throws IOException {
verify(readonlyFlag, Mockito.never()).assertWritable();
}
+ @Test
+ @DisplayName("create new and atomically set file attributes")
+ public void testNewFileChannelCreate3() throws IOException {
+ Mockito.doReturn(10).when(fileSystemProperties).maxCleartextNameLength();
+ var attrs = PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwxr-x---"));
+
+ FileChannel ch = inTest.newFileChannel(cleartextPath, EnumSet.of(StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE), attrs);
+
+ Assertions.assertSame(fileChannel, ch);
+ verify(openCryptoFile).newFileChannel(Mockito.any(), Mockito.eq(attrs));
+ }
+
}
@Test
diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java
index 185dbc08..75c808e8 100644
--- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java
@@ -49,6 +49,7 @@ public void testSetMasterkeyFilenameAndReadonlyFlag() {
anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), //
anEntry(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename), //
anEntry(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, DEFAULT_MAX_CLEARTEXT_NAME_LENGTH), //
+ anEntry(PROPERTY_SHORTENING_THRESHOLD, DEFAULT_SHORTENING_THRESHOLD), //
anEntry(PROPERTY_CIPHER_COMBO, DEFAULT_CIPHER_COMBO), //
anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.of(FileSystemFlags.READONLY))));
}
@@ -60,18 +61,21 @@ public void testFromMap() {
map.put(PROPERTY_KEYLOADER, keyLoader);
map.put(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename);
map.put(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, 255);
+ map.put(PROPERTY_SHORTENING_THRESHOLD, 221);
map.put(PROPERTY_FILESYSTEM_FLAGS, EnumSet.of(FileSystemFlags.READONLY));
CryptoFileSystemProperties inTest = cryptoFileSystemPropertiesFrom(map).build();
MatcherAssert.assertThat(inTest.masterkeyFilename(), is(masterkeyFilename));
MatcherAssert.assertThat(inTest.readonly(), is(true));
MatcherAssert.assertThat(inTest.maxCleartextNameLength(), is(255));
+ MatcherAssert.assertThat(inTest.shorteningThreshold(), is(221));
MatcherAssert.assertThat(inTest.entrySet(),
containsInAnyOrder( //
anEntry(PROPERTY_KEYLOADER, keyLoader), //
anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), //
anEntry(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename), //
anEntry(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, 255), //
+ anEntry(PROPERTY_SHORTENING_THRESHOLD, 221), //
anEntry(PROPERTY_CIPHER_COMBO, DEFAULT_CIPHER_COMBO), //
anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.of(FileSystemFlags.READONLY))));
}
@@ -93,6 +97,7 @@ public void testWrapMapWithTrueReadonly() {
anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), //
anEntry(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename), //
anEntry(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, DEFAULT_MAX_CLEARTEXT_NAME_LENGTH), //
+ anEntry(PROPERTY_SHORTENING_THRESHOLD, DEFAULT_SHORTENING_THRESHOLD), //
anEntry(PROPERTY_CIPHER_COMBO, DEFAULT_CIPHER_COMBO), //
anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.of(FileSystemFlags.READONLY))));
}
@@ -114,6 +119,7 @@ public void testWrapMapWithFalseReadonly() {
anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), //
anEntry(PROPERTY_MASTERKEY_FILENAME, masterkeyFilename), //
anEntry(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, DEFAULT_MAX_CLEARTEXT_NAME_LENGTH), //
+ anEntry(PROPERTY_SHORTENING_THRESHOLD, DEFAULT_SHORTENING_THRESHOLD), //
anEntry(PROPERTY_CIPHER_COMBO, DEFAULT_CIPHER_COMBO), //
anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.noneOf(FileSystemFlags.class))));
}
@@ -165,6 +171,7 @@ public void testWrapMapWithoutReadonly() {
anEntry(PROPERTY_VAULTCONFIG_FILENAME, DEFAULT_VAULTCONFIG_FILENAME), //
anEntry(PROPERTY_MASTERKEY_FILENAME, DEFAULT_MASTERKEY_FILENAME), //
anEntry(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, DEFAULT_MAX_CLEARTEXT_NAME_LENGTH), //
+ anEntry(PROPERTY_SHORTENING_THRESHOLD, DEFAULT_SHORTENING_THRESHOLD), //
anEntry(PROPERTY_CIPHER_COMBO, DEFAULT_CIPHER_COMBO), //
anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.noneOf(FileSystemFlags.class))
)
diff --git a/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java
index 3c04a6fa..a480b8c6 100644
--- a/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java
@@ -139,7 +139,7 @@ public void testDeleteDirectoryContainingLongNamedDirectory() throws IOException
// a
// .. LongNameaaa...
- String name = "LongName" + Strings.repeat("a", Constants.DEFAULT_SHORTENING_THRESHOLD);
+ String name = "LongName" + Strings.repeat("a", CryptoFileSystemProperties.DEFAULT_SHORTENING_THRESHOLD);
createFolder(cleartextDirectory, name);
Assertions.assertThrows(DirectoryNotEmptyException.class, () -> {
diff --git a/src/test/java/org/cryptomator/cryptofs/attr/CryptoBasicFileAttributesTest.java b/src/test/java/org/cryptomator/cryptofs/attr/CryptoBasicFileAttributesTest.java
index ddbd8675..ef12ab0f 100644
--- a/src/test/java/org/cryptomator/cryptofs/attr/CryptoBasicFileAttributesTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/attr/CryptoBasicFileAttributesTest.java
@@ -89,9 +89,8 @@ public void testSizeOfDirectory() {
@Test
public void testSizeOfSymlink() {
- Mockito.when(delegateAttr.size()).thenReturn(123l);
BasicFileAttributes attr = new CryptoBasicFileAttributes(delegateAttr, SYMLINK, ciphertextFilePath, cryptor, Optional.empty());
- Assertions.assertEquals(123l, attr.size());
+ Assertions.assertEquals(1337l, attr.size());
}
@Test
diff --git a/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFileTest.java b/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFileTest.java
index 13873697..343aa2bd 100644
--- a/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFileTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFileTest.java
@@ -1,5 +1,7 @@
package org.cryptomator.cryptofs.fh;
+import com.google.common.jimfs.Configuration;
+import com.google.common.jimfs.Feature;
import com.google.common.jimfs.Jimfs;
import org.cryptomator.cryptofs.EffectiveOpenOptions;
import org.cryptomator.cryptofs.ReadonlyFlag;
@@ -28,6 +30,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.PosixFilePermissions;
import java.time.Instant;
import java.util.EnumSet;
import java.util.concurrent.atomic.AtomicLong;
@@ -55,7 +58,7 @@ public class OpenCryptoFileTest {
@BeforeAll
public static void setup() {
- FS = Jimfs.newFileSystem("OpenCryptoFileTest");
+ FS = Jimfs.newFileSystem("OpenCryptoFileTest", Configuration.unix().toBuilder().setAttributeViews("basic", "posix").build());
CURRENT_FILE_PATH = new AtomicReference<>(FS.getPath("currentFile"));
}
@@ -131,8 +134,9 @@ public void testGetSizeBeforeCreatingFileChannel() {
@Order(10)
@DisplayName("create first FileChannel")
public void createFileChannel() throws IOException {
+ var attrs = PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwxr-x---"));
EffectiveOpenOptions options = EffectiveOpenOptions.from(EnumSet.of(StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE), readonlyFlag);
- FileChannel ch = openCryptoFile.newFileChannel(options);
+ FileChannel ch = openCryptoFile.newFileChannel(options, attrs);
Assertions.assertSame(cleartextFileChannel, ch);
verify(chunkIO).registerChannel(ciphertextChannel.get(), true);
}
diff --git a/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFilesTest.java b/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFilesTest.java
index a60e65af..ea71bd91 100644
--- a/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFilesTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFilesTest.java
@@ -67,13 +67,11 @@ public void testGetOrCreate() {
public void testWriteCiphertextFile() throws IOException {
Path path = Paths.get("/foo");
EffectiveOpenOptions openOptions = Mockito.mock(EffectiveOpenOptions.class);
- ByteBuffer contents = Mockito.mock(ByteBuffer.class);
+ ByteBuffer contents = StandardCharsets.UTF_8.encode("hello world");
inTest.writeCiphertextFile(path, openOptions, contents);
- ArgumentCaptor bytesWritten = ArgumentCaptor.forClass(ByteBuffer.class);
- Mockito.verify(ciphertextFileChannel).write(bytesWritten.capture());
- Assertions.assertEquals(contents, bytesWritten.getValue());
+ Mockito.verify(ciphertextFileChannel).write(contents);
}
@Test
diff --git a/src/test/java/org/cryptomator/cryptofs/health/dirid/DirIdCheckTest.java b/src/test/java/org/cryptomator/cryptofs/health/dirid/DirIdCheckTest.java
index 2a73e9f5..9fd437e6 100644
--- a/src/test/java/org/cryptomator/cryptofs/health/dirid/DirIdCheckTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/health/dirid/DirIdCheckTest.java
@@ -38,6 +38,8 @@ public class DirIdCheckTest {
BB/bbbb/bar=.c9r/dir.c9r = ffffffffffff-aaaaaaaaaaaa-tttttttttttt
BB/bbbb/baz=.c9r/dir.c9r = [EMPTY]
BB/bbbb/foo=.c9r/unrelated/dir.c9r = unrelatedfile
+ BB/bbbb/missing=.c9r
+ BB/bbbb/dir.c9r = loose
CC/cccc/foo=.c9r = file
""";
@@ -116,10 +118,21 @@ public void testVisitorDetectsReusedDirId() throws IOException {
public void testVisitorDetectsObeseDirId() throws IOException {
Files.walkFileTree(dataRoot, Set.of(), 4, visitor);
- Predicate expectedObeseFile = obeseDirFile -> "/d/BB/bbbb/bar=.c9r/dir.c9r".equals(obeseDirFile.dirFile.toString());
+ Predicate expectedObeseFile = obeseDirFile -> "/d/BB/bbbb/bar=.c9r/dir.c9r".equals(obeseDirFile.dirIdFile.toString());
ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(DiagnosticResult.class);
Mockito.verify(resultsCollector, Mockito.atLeastOnce()).accept(resultCaptor.capture());
- MatcherAssert.assertThat(resultCaptor.getAllValues(), Matchers.hasItem(CustomMatchers.matching(ObeseDirFile.class, expectedObeseFile, "Obese dir file: /d/BB/bbbb/bar=.c9r/dir.c9r")));
+ MatcherAssert.assertThat(resultCaptor.getAllValues(), Matchers.hasItem(CustomMatchers.matching(ObeseDirIdFile.class, expectedObeseFile, "Obese dir file: /d/BB/bbbb/bar=.c9r/dir.c9r")));
+ }
+
+ @Test
+ @DisplayName("detects loose dirID in /d/BB/bbbb/dir.c9r")
+ public void testVisitorDetectsLooseDirId() throws IOException {
+ Files.walkFileTree(dataRoot, Set.of(), 4, visitor);
+
+ Predicate expectedLooseFile = looseDirIdFile -> "/d/BB/bbbb/dir.c9r".equals(looseDirIdFile.dirIdFile.toString());
+ ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(DiagnosticResult.class);
+ Mockito.verify(resultsCollector, Mockito.atLeastOnce()).accept(resultCaptor.capture());
+ MatcherAssert.assertThat(resultCaptor.getAllValues(), Matchers.hasItem(CustomMatchers.matching(LooseDirIdFile.class, expectedLooseFile, "Obese dir file: /d/BB/bbbb/bar=.c9r/dir.c9r")));
}
@Test
@@ -127,10 +140,21 @@ public void testVisitorDetectsObeseDirId() throws IOException {
public void testVisitorDetectsEmptyDirId() throws IOException {
Files.walkFileTree(dataRoot, Set.of(), 4, visitor);
- Predicate expectedEmptyFile = emptyDirFile -> "/d/BB/bbbb/baz=.c9r/dir.c9r".equals(emptyDirFile.dirFile.toString());
+ Predicate expectedEmptyFile = emptyDirFile -> "/d/BB/bbbb/baz=.c9r/dir.c9r".equals(emptyDirFile.dirIdFile.toString());
+ ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(DiagnosticResult.class);
+ Mockito.verify(resultsCollector, Mockito.atLeastOnce()).accept(resultCaptor.capture());
+ MatcherAssert.assertThat(resultCaptor.getAllValues(), Matchers.hasItem(CustomMatchers.matching(EmptyDirIdFile.class, expectedEmptyFile, "Empty dir file: /d/BB/bbbb/baz=.c9r/dir.c9r")));
+ }
+
+ @Test
+ @DisplayName("detects missing dirId in /d/BB/bbbb/missing.c9r")
+ public void testVisitorDetectsMissingDirId() throws IOException {
+ Files.walkFileTree(dataRoot, Set.of(), 4, visitor);
+
+ Predicate expectedMissingFile = missingDirIdFile -> "/d/BB/bbbb/missing=.c9r".equals(missingDirIdFile.c9rDirectory.toString());
ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(DiagnosticResult.class);
Mockito.verify(resultsCollector, Mockito.atLeastOnce()).accept(resultCaptor.capture());
- MatcherAssert.assertThat(resultCaptor.getAllValues(), Matchers.hasItem(CustomMatchers.matching(EmptyDirFile.class, expectedEmptyFile, "Empty dir file: /d/BB/bbbb/baz=.c9r/dir.c9r")));
+ MatcherAssert.assertThat(resultCaptor.getAllValues(), Matchers.hasItem(CustomMatchers.matching(MissingDirIdFile.class, expectedMissingFile, "Missing dirId file for: /d/BB/bbbb/missing=.c9r")));
}
}
diff --git a/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingContentDirTest.java b/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingContentDirTest.java
new file mode 100644
index 00000000..8a73f7bd
--- /dev/null
+++ b/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingContentDirTest.java
@@ -0,0 +1,71 @@
+package org.cryptomator.cryptofs.health.dirid;
+
+import org.cryptomator.cryptofs.CryptoPathMapper;
+import org.cryptomator.cryptofs.VaultConfig;
+import org.cryptomator.cryptolib.api.Cryptor;
+import org.cryptomator.cryptolib.api.FileNameCryptor;
+import org.cryptomator.cryptolib.api.Masterkey;
+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.api.io.TempDir;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mockito;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+
+public class MissingContentDirTest {
+
+ @TempDir
+ public Path pathToVault;
+
+ private MissingContentDir result;
+ private String dirId;
+ private Cryptor cryptor;
+ private FileNameCryptor fileNameCryptor;
+
+ @BeforeEach
+ public void init() {
+ Path p = Mockito.mock(Path.class, "ignored");
+ dirId = "1234-456789-1234";
+ result = new MissingContentDir(dirId, p);
+
+ cryptor = Mockito.mock(Cryptor.class);
+ fileNameCryptor = Mockito.mock(FileNameCryptor.class);
+ Mockito.doReturn(fileNameCryptor).when(cryptor).fileNameCryptor();
+ Mockito.doReturn(fileNameCryptor).when(cryptor).fileNameCryptor();
+ }
+
+ @DisplayName("After fix the content dir including dirId file exists ")
+ @Test
+ public void testFix() throws IOException {
+ var dirIdHash = "ridiculous-30-char-pseudo-hash";
+ Mockito.doReturn(dirIdHash).when(fileNameCryptor).hashDirectoryId(dirId);
+ var resultSpy = Mockito.spy(result);
+ Mockito.doNothing().when(resultSpy).createDirIdBackupFile(Mockito.any(), Mockito.any());
+
+ resultSpy.fix(pathToVault, Mockito.mock(VaultConfig.class), Mockito.mock(Masterkey.class), cryptor);
+
+ var expectedPath = pathToVault.resolve("d/ri/diculous-30-char-pseudo-hash");
+ ArgumentMatcher cipherDirMatcher = obj -> obj.dirId.equals(dirId) && obj.path.endsWith(expectedPath);
+ Mockito.verify(resultSpy, Mockito.times(1)).createDirIdBackupFile(Mockito.eq(cryptor), Mockito.argThat(cipherDirMatcher));
+ var attr = Assertions.assertDoesNotThrow(() -> Files.readAttributes(expectedPath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS));
+ Assertions.assertTrue(attr.isDirectory());
+ }
+
+ @DisplayName("If dirId file creation fails, fix fails ")
+ @Test
+ public void testFixFailsOnFailingDirIdFile() throws IOException {
+ var dirIdHash = "ridiculous-30-char-pseudo-hash";
+ Mockito.doReturn(dirIdHash).when(fileNameCryptor).hashDirectoryId(dirId);
+ var resultSpy = Mockito.spy(result);
+ Mockito.doThrow(new IOException("Access denied")).when(resultSpy).createDirIdBackupFile(Mockito.any(), Mockito.any());
+
+ Assertions.assertThrows(IOException.class, () -> resultSpy.fix(pathToVault, Mockito.mock(VaultConfig.class), Mockito.mock(Masterkey.class), cryptor));
+ }
+}
diff --git a/src/test/java/org/cryptomator/cryptofs/health/dirid/OrphanDirTest.java b/src/test/java/org/cryptomator/cryptofs/health/dirid/OrphanDirTest.java
index b4c736dd..dbe177ac 100644
--- a/src/test/java/org/cryptomator/cryptofs/health/dirid/OrphanDirTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/health/dirid/OrphanDirTest.java
@@ -38,7 +38,7 @@ public class OrphanDirTest {
@TempDir
public Path pathToVault;
- private OrphanDir result;
+ private OrphanContentDir result;
private Path dataDir;
private Path cipherRoot;
private Path cipherRecovery;
@@ -49,7 +49,7 @@ public class OrphanDirTest {
@BeforeEach
public void init() throws IOException {
Path p = Mockito.mock(Path.class, "ignored");
- result = new OrphanDir(p);
+ result = new OrphanContentDir(p);
dataDir = pathToVault.resolve("d");
cipherRoot = dataDir.resolve("00/0000");
@@ -220,7 +220,7 @@ public void testPrepareStepParentOrphanedStepParentDir() throws IOException {
@Nested
class RetrieveDirIdTests {
- private OrphanDir resultSpy;
+ private OrphanContentDir resultSpy;
@BeforeEach
public void init() {
@@ -418,7 +418,7 @@ public void testAdoptOrphanedShortenedMissingNameC9s() throws IOException {
@Test
@DisplayName("fix() prepares vault, process every resource in orphanDir and deletes orphanDir (dirId not present)")
public void testFixNoDirId() throws IOException {
- result = new OrphanDir(dataDir.relativize(cipherOrphan));
+ result = new OrphanContentDir(dataDir.relativize(cipherOrphan));
var resultSpy = Mockito.spy(result);
Path orphan1 = cipherOrphan.resolve("orphan1.c9r");
@@ -448,7 +448,7 @@ public void testFixNoDirId() throws IOException {
@Test
@DisplayName("fix() does not choke when filename cannot be restored")
public void testFixContinuesOnNotRecoverableFilename() throws IOException {
- result = new OrphanDir(dataDir.relativize(cipherOrphan));
+ result = new OrphanContentDir(dataDir.relativize(cipherOrphan));
var resultSpy = Mockito.spy(result);
Path orphan1 = cipherOrphan.resolve("orphan1.c9r");
@@ -485,7 +485,7 @@ public void testFixContinuesOnNotRecoverableFilename() throws IOException {
@Test
@DisplayName("fix() prepares vault, process every resource (except dirId file) in orphanDir and deletes orphanDir (dirId present)")
public void testFixWithDirId() throws IOException {
- result = new OrphanDir(dataDir.relativize(cipherOrphan));
+ result = new OrphanContentDir(dataDir.relativize(cipherOrphan));
var resultSpy = Mockito.spy(result);
var lostName1 = "Brother.sibling";
@@ -533,7 +533,7 @@ public void testFixRepeated() throws IOException {
AtomicReference clearStepparentNameRef = new AtomicReference<>("");
- var interruptedResult = new OrphanDir(dataDir.relativize(cipherOrphan));
+ var interruptedResult = new OrphanContentDir(dataDir.relativize(cipherOrphan));
var interruptedSpy = Mockito.spy(interruptedResult);
Mockito.doReturn(cipherRecovery).when(interruptedSpy).prepareRecoveryDir(pathToVault, fileNameCryptor);
Mockito.doAnswer(invocation -> {
@@ -541,7 +541,7 @@ public void testFixRepeated() throws IOException {
throw new IOException("Interrupt");
}).when(interruptedSpy).prepareStepParent(Mockito.eq(dataDir), Mockito.eq(cipherRecovery), Mockito.eq(cryptor), Mockito.any());
- var continuedResult = new OrphanDir(dataDir.relativize(cipherOrphan));
+ var continuedResult = new OrphanContentDir(dataDir.relativize(cipherOrphan));
var continuedSpy = Mockito.spy(continuedResult);
Mockito.doReturn(cipherRecovery).when(continuedSpy).prepareRecoveryDir(pathToVault, fileNameCryptor);
Mockito.doThrow(IOException.class).when(continuedSpy).prepareStepParent(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
@@ -557,7 +557,7 @@ public void testFixRepeated() throws IOException {
@DisplayName("orphaned recovery dir will only be reintegrated")
public void testFixOrphanedRecoveryDir() throws IOException {
Path orphanedRecovery = dataDir.resolve("11/1111");
- result = new OrphanDir(dataDir.relativize(orphanedRecovery));
+ result = new OrphanContentDir(dataDir.relativize(orphanedRecovery));
var resultSpy = Mockito.spy(result);
Path orphan1 = orphanedRecovery.resolve("orphan1.c9r");
diff --git a/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
deleted file mode 100644
index ca6ee9ce..00000000
--- a/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
+++ /dev/null
@@ -1 +0,0 @@
-mock-maker-inline
\ No newline at end of file