diff --git a/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java b/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java index 0dd1a748..33570fd1 100644 --- a/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java +++ b/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java @@ -35,13 +35,15 @@ class C9rConflictResolver { private final Cryptor cryptor; private final byte[] dirId; - private final VaultConfig vaultConfig; + private final int maxC9rFileNameLength; + private final int maxCleartextFileNameLength; @Inject public C9rConflictResolver(Cryptor cryptor, @Named("dirId") String dirId, VaultConfig vaultConfig) { this.cryptor = cryptor; this.dirId = dirId.getBytes(StandardCharsets.US_ASCII); - this.vaultConfig = vaultConfig; + this.maxC9rFileNameLength = vaultConfig.getShorteningThreshold(); + this.maxCleartextFileNameLength = (maxC9rFileNameLength - 4) / 4 * 3 - 16; // math from FileSystemCapabilityChecker.determineSupportedCleartextFileNameLength() } public Stream process(Node node) { @@ -80,13 +82,6 @@ private Stream resolveConflict(Node conflicting, Path canonicalPath) throw } } - // visible for testing - int calcMaxCleartextNameLength(int maxCiphertextNameLength) { - // math explained in https://github.com/cryptomator/cryptofs/issues/60#issuecomment-523238303; - // subtract 4 for file extension, base64-decode, subtract 16 for IV - return (maxCiphertextNameLength - 4) / 4 * 3 - 16; - } - /** * Resolves a conflict by renaming the conflicting file. * @@ -99,11 +94,9 @@ int calcMaxCleartextNameLength(int maxCiphertextNameLength) { private Node renameConflictingFile(Path canonicalPath, Path conflictingPath, String cleartext) throws IOException { assert Files.exists(canonicalPath); final int beginOfFileExtension = cleartext.lastIndexOf('.'); - final int maxCiphertextNameLength = vaultConfig.getShorteningThreshold(); - final int maxCleartextNameLength = calcMaxCleartextNameLength(maxCiphertextNameLength); final String fileExtension = (beginOfFileExtension > 0) ? cleartext.substring(beginOfFileExtension) : ""; final String basename = (beginOfFileExtension > 0) ? cleartext.substring(0, beginOfFileExtension) : cleartext; - final String lengthRestrictedBasename = basename.substring(0, Math.min(basename.length(), maxCleartextNameLength - fileExtension.length() - 5)); // 5 chars for conflict suffix " (42)" + final String lengthRestrictedBasename = basename.substring(0, Math.min(basename.length(), maxCleartextFileNameLength - fileExtension.length() - 5)); // 5 chars for conflict suffix " (42)" String alternativeCleartext; String alternativeCiphertext; String alternativeCiphertextName; @@ -115,7 +108,7 @@ private Node renameConflictingFile(Path canonicalPath, Path conflictingPath, Str alternativeCiphertextName = alternativeCiphertext + Constants.CRYPTOMATOR_FILE_SUFFIX; alternativePath = canonicalPath.resolveSibling(alternativeCiphertextName); } while (Files.exists(alternativePath)); - assert alternativeCiphertextName.length() <= maxCiphertextNameLength; + assert alternativeCiphertextName.length() <= maxC9rFileNameLength; LOG.info("Moving conflicting file {} to {}", conflictingPath, alternativePath); Files.move(conflictingPath, alternativePath, StandardCopyOption.ATOMIC_MOVE); Node node = new Node(alternativePath); diff --git a/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java b/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java index 096bd538..75113ae3 100644 --- a/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java +++ b/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java @@ -31,7 +31,7 @@ public void setup() { fileNameCryptor = Mockito.mock(FileNameCryptor.class); vaultConfig = Mockito.mock(VaultConfig.class); Mockito.when(cryptor.fileNameCryptor()).thenReturn(fileNameCryptor); - Mockito.when(vaultConfig.getShorteningThreshold()).thenReturn(220); + Mockito.when(vaultConfig.getShorteningThreshold()).thenReturn(44); // results in max cleartext size = 14 conflictResolver = new C9rConflictResolver(cryptor, "foo", vaultConfig); } @@ -77,6 +77,25 @@ public void testResolveConflictingFileByChoosingNewName(@TempDir Path dir) throw Assertions.assertFalse(Files.exists(unresolved.ciphertextPath)); } + @Test + public void testResolveConflictingFileByChoosingNewLengthLimitedName(@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 = "hello world.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("hello (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")); @@ -131,11 +150,4 @@ public void testResolveConflictingSymlinkTrivially(@TempDir Path dir) throws IOE Assertions.assertFalse(Files.exists(unresolved.ciphertextPath)); } - @ParameterizedTest - @CsvSource({"220, 146", "219, 143", "218, 143", "217, 143", "216, 143", "215, 140"}) - public void testCalcMaxCleartextNameLength(int input, int expectedResult) { - int result = conflictResolver.calcMaxCleartextNameLength(input); - Assertions.assertEquals(expectedResult, result); - } - } \ No newline at end of file