diff --git a/pom.xml b/pom.xml
index a6c45f61..9746be12 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2,7 +2,7 @@
4.0.0
org.cryptomator
cryptofs
- 1.9.6
+ 1.9.7
Cryptomator Crypto Filesystem
This library provides the Java filesystem provider used by Cryptomator.
https://github.com/cryptomator/cryptofs
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 4a08097d..7f33177a 100644
--- a/src/main/java/org/cryptomator/cryptofs/migration/v7/FilePathMigration.java
+++ b/src/main/java/org/cryptomator/cryptofs/migration/v7/FilePathMigration.java
@@ -44,7 +44,7 @@ class FilePathMigration {
private final String oldCanonicalName;
/**
- * @param oldPath The actual file path before migration
+ * @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) {
@@ -57,7 +57,7 @@ class FilePathMigration {
* 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.
+ * @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
*/
@@ -89,7 +89,7 @@ public static Optional parse(Path vaultRoot, Path oldPath) th
/**
* 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 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.
@@ -151,7 +151,7 @@ public Path migrate() throws IOException {
* @return The path after successful migration of {@link #oldPath} if migration is successful for the given attemptSuffix
*/
// visible for testing
- Path getTargetPath(String attemptSuffix) {
+ Path getTargetPath(String attemptSuffix) throws InvalidOldFilenameException {
final String canonicalInflatedName = getNewInflatedName();
final String canonicalDeflatedName = getNewDeflatedName();
final boolean isShortened = !canonicalInflatedName.equals(canonicalDeflatedName);
@@ -177,7 +177,7 @@ Path getTargetPath(String attemptSuffix) {
}
}
}
-
+
public Path getOldPath() {
return oldPath;
}
@@ -202,19 +202,34 @@ String getOldCanonicalNameWithoutTypePrefix() {
}
/**
- * @return BASE64-encode(BASE32-decode({@link #getOldCanonicalNameWithoutTypePrefix oldCanonicalNameWithoutPrefix})) + {@value #NEW_REGULAR_SUFFIX}
+ * @return BASE64-encode({@link #getDecodedCiphertext oldDecodedCiphertext}) + {@value #NEW_REGULAR_SUFFIX}
+ * @throws InvalidOldFilenameException if failing to base32-decode the old filename
*/
// visible for testing
- String getNewInflatedName() {
- byte[] decoded = BASE32.decode(getOldCanonicalNameWithoutTypePrefix());
+ String getNewInflatedName() throws InvalidOldFilenameException {
+ byte[] decoded = getDecodedCiphertext();
return BASE64.encode(decoded) + NEW_REGULAR_SUFFIX;
}
+ /**
+ * @return BASE32-decode({@link #getOldCanonicalNameWithoutTypePrefix oldCanonicalNameWithoutPrefix})
+ * @throws InvalidOldFilenameException if failing to base32-decode the old filename
+ */
+ // visible for testing
+ byte[] getDecodedCiphertext() throws InvalidOldFilenameException {
+ String encodedCiphertext = getOldCanonicalNameWithoutTypePrefix();
+ try {
+ return BASE32.decode(encodedCiphertext);
+ } catch (IllegalArgumentException e) {
+ throw new InvalidOldFilenameException("Can't base32-decode '" + encodedCiphertext + "' in file " + oldPath.toString(), e);
+ }
+ }
+
/**
* @return {@link #getNewInflatedName() newInflatedName} if it is shorter than {@link #SHORTENING_THRESHOLD}, else BASE64(SHA1(newInflatedName)) + ".c9s"
*/
// visible for testing
- String getNewDeflatedName() {
+ String getNewDeflatedName() throws InvalidOldFilenameException {
String inflatedName = getNewInflatedName();
if (inflatedName.length() > SHORTENING_THRESHOLD) {
byte[] longFileNameBytes = inflatedName.getBytes(UTF_8);
diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v7/InvalidOldFilenameException.java b/src/main/java/org/cryptomator/cryptofs/migration/v7/InvalidOldFilenameException.java
new file mode 100644
index 00000000..fe45fde9
--- /dev/null
+++ b/src/main/java/org/cryptomator/cryptofs/migration/v7/InvalidOldFilenameException.java
@@ -0,0 +1,10 @@
+package org.cryptomator.cryptofs.migration.v7;
+
+import java.io.IOException;
+
+public class InvalidOldFilenameException extends IOException {
+
+ public InvalidOldFilenameException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/cryptomator/cryptofs/migration/v7/FilePathMigrationTest.java b/src/test/java/org/cryptomator/cryptofs/migration/v7/FilePathMigrationTest.java
index e2962915..120592bb 100644
--- a/src/test/java/org/cryptomator/cryptofs/migration/v7/FilePathMigrationTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/migration/v7/FilePathMigrationTest.java
@@ -15,6 +15,9 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.converter.ArgumentConversionException;
+import org.junit.jupiter.params.converter.ConvertWith;
+import org.junit.jupiter.params.converter.SimpleArgumentConverter;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.Mockito;
@@ -25,6 +28,7 @@
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.Base64;
import java.util.Optional;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -44,6 +48,31 @@ public void testGetOldCanonicalNameWithoutTypePrefix(String oldCanonicalName, St
Assertions.assertEquals(expectedResult, migration.getOldCanonicalNameWithoutTypePrefix());
}
+
+ @ParameterizedTest(name = "getDecodedCiphertext() expected to be {1} for {0}")
+ @CsvSource({
+ "ORSXG5A=,dGVzdA==",
+ "0ORSXG5A=,dGVzdA==",
+ "1SORSXG5A=,dGVzdA==",
+ })
+ public void testGetDecodedCiphertext(String oldCanonicalName, @ConvertWith(ByteArrayArgumentConverter.class) byte[] expected) throws InvalidOldFilenameException {
+ FilePathMigration migration = new FilePathMigration(oldPath, oldCanonicalName);
+ Assertions.assertArrayEquals(expected, migration.getDecodedCiphertext());
+ }
+
+ @ParameterizedTest(name = "getDecodedCiphertext() throws InvalidOldFilenameException for {0}")
+ @ValueSource(strings = {
+ "ORSXG5=A",
+ "ORSX=G5A"
+ })
+ public void testMalformedGetDecodedCiphertext(String oldCanonicalName) {
+ FilePathMigration migration = new FilePathMigration(oldPath, oldCanonicalName);
+
+ InvalidOldFilenameException e = Assertions.assertThrows(InvalidOldFilenameException.class, () -> {
+ migration.getDecodedCiphertext();
+ });
+ Assertions.assertTrue(e.getMessage().contains(oldPath.toString()));
+ }
@ParameterizedTest(name = "getNewInflatedName() expected to be {1} for {0}")
@CsvSource({
@@ -51,7 +80,7 @@ public void testGetOldCanonicalNameWithoutTypePrefix(String oldCanonicalName, St
"0ORSXG5A=,dGVzdA==.c9r",
"1SORSXG5A=,dGVzdA==.c9r",
})
- public void testGetNewInflatedName(String oldCanonicalName, String expectedResult) {
+ public void testGetNewInflatedName(String oldCanonicalName, String expectedResult) throws InvalidOldFilenameException {
FilePathMigration migration = new FilePathMigration(oldPath, oldCanonicalName);
Assertions.assertEquals(expectedResult, migration.getNewInflatedName());
@@ -63,7 +92,7 @@ public void testGetNewInflatedName(String oldCanonicalName, String expectedResul
"ORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSQ====,dGVzdCB0ZXN0IHRlc3QgdGVzdCB0ZXN0IHRlc3QgdGVzdCB0ZXN0IHRlc3QgdGVzdCB0ZXN0IHRlc3QgdGVzdCB0ZXN0IHRlc3QgdGVzdCB0ZXN0IHRlc3QgdGVzdCB0ZXN0IHRlc3QgdGVzdCB0ZXN0IHRlc3QgdGVzdCB0ZXN0IHRlc3QgdGVzdCB0ZXN0IHRlc3QgdGVzdCB0ZXN0IHRl.c9r",
"ORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG===,30xtS3YjsiMJRwu1oAVc_0S2aAU=.c9s",
})
- public void testGetNewDeflatedName(String oldCanonicalName, String expectedResult) {
+ public void testGetNewDeflatedName(String oldCanonicalName, String expectedResult) throws InvalidOldFilenameException {
FilePathMigration migration = new FilePathMigration(oldPath, oldCanonicalName);
Assertions.assertEquals(expectedResult, migration.getNewDeflatedName());
@@ -104,7 +133,7 @@ public void testIsSymlink(String oldCanonicalName, boolean expectedResult) {
"ORSXG5A=,'_1',dGVzdA==_1.c9r",
"ORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG5BAORSXG===,'_123',30xtS3YjsiMJRwu1oAVc_0S2aAU=_123.c9s/contents.c9r",
})
- public void testGetTargetPath(String oldCanonicalName, String attemptSuffix, String expected) {
+ public void testGetTargetPath(String oldCanonicalName, String attemptSuffix, String expected) throws InvalidOldFilenameException {
Path old = Paths.get("/tmp/foo");
FilePathMigration migration = new FilePathMigration(old, oldCanonicalName);
@@ -325,4 +354,12 @@ public void testMigrateShortened(String oldPathStr, String metadataFilePath, Str
}
+ public static class ByteArrayArgumentConverter extends SimpleArgumentConverter {
+
+ @Override
+ protected Object convert(Object source, Class> targetType) throws ArgumentConversionException {
+ Assertions.assertEquals(byte[].class, targetType, "Can only convert to byte[]");
+ return Base64.getUrlDecoder().decode(String.valueOf(source));
+ }
+ }
}