From 78645ecdf66ae7f195d7d954624d96cb99c362f6 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Sat, 7 May 2016 14:40:44 +0200 Subject: [PATCH] fixes #264 --- main/filesystem-charsets/pom.xml | 45 ++++++ .../charsets/NormalizedNameFile.java | 32 ++++ .../charsets/NormalizedNameFileSystem.java | 27 ++++ .../charsets/NormalizedNameFolder.java | 76 +++++++++ .../filesystem/charsets/package-info.java | 16 ++ .../NormalizedNameFileSystemTest.java | 90 +++++++++++ .../charsets/NormalizedNameFileTest.java | 48 ++++++ .../charsets/NormalizedNameFolderTest.java | 149 ++++++++++++++++++ .../src/test/resources/log4j2.xml | 22 +++ main/filesystem-invariants-tests/pom.xml | 4 + .../invariants/FileSystemFactories.java | 22 ++- main/pom.xml | 6 + main/ui/pom.xml | 4 + .../java/org/cryptomator/ui/model/Vault.java | 4 +- 14 files changed, 541 insertions(+), 4 deletions(-) create mode 100644 main/filesystem-charsets/pom.xml create mode 100644 main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFile.java create mode 100644 main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFileSystem.java create mode 100644 main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFolder.java create mode 100644 main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/package-info.java create mode 100644 main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFileSystemTest.java create mode 100644 main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFileTest.java create mode 100644 main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFolderTest.java create mode 100644 main/filesystem-charsets/src/test/resources/log4j2.xml diff --git a/main/filesystem-charsets/pom.xml b/main/filesystem-charsets/pom.xml new file mode 100644 index 0000000000..b5eba0555a --- /dev/null +++ b/main/filesystem-charsets/pom.xml @@ -0,0 +1,45 @@ + + + + 4.0.0 + + org.cryptomator + main + 1.1.0-SNAPSHOT + + filesystem-charsets + Cryptomator filesystem: Filename charset compatibility layer + + + + org.cryptomator + filesystem-api + + + + + org.cryptomator + commons-test + + + org.cryptomator + filesystem-inmemory + + + + + + + org.jacoco + jacoco-maven-plugin + + + + \ No newline at end of file diff --git a/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFile.java b/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFile.java new file mode 100644 index 0000000000..af73d59c13 --- /dev/null +++ b/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFile.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2016 Sebastian Stenzel and others. + * This file is licensed under the terms of the MIT license. + * See the LICENSE.txt file for more info. + * + * Contributors: + * Sebastian Stenzel - initial API and implementation + *******************************************************************************/ +package org.cryptomator.filesystem.charsets; + +import java.io.UncheckedIOException; +import java.text.Normalizer; +import java.text.Normalizer.Form; + +import org.cryptomator.filesystem.File; +import org.cryptomator.filesystem.delegating.DelegatingFile; + +class NormalizedNameFile extends DelegatingFile { + + private final Form displayForm; + + public NormalizedNameFile(NormalizedNameFolder parent, File delegate, Form displayForm) { + super(parent, delegate); + this.displayForm = displayForm; + } + + @Override + public String name() throws UncheckedIOException { + return Normalizer.normalize(super.name(), displayForm); + } + +} diff --git a/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFileSystem.java b/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFileSystem.java new file mode 100644 index 0000000000..a69c572d6f --- /dev/null +++ b/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFileSystem.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2016 Sebastian Stenzel and others. + * This file is licensed under the terms of the MIT license. + * See the LICENSE.txt file for more info. + * + * Contributors: + * Sebastian Stenzel - initial API and implementation + *******************************************************************************/ +package org.cryptomator.filesystem.charsets; + +import java.text.Normalizer.Form; + +import org.cryptomator.filesystem.Folder; +import org.cryptomator.filesystem.delegating.DelegatingFileSystem; + +public class NormalizedNameFileSystem extends NormalizedNameFolder implements DelegatingFileSystem { + + public NormalizedNameFileSystem(Folder delegate, Form displayForm) { + super(null, delegate, displayForm); + } + + @Override + public Folder getDelegate() { + return delegate; + } + +} diff --git a/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFolder.java b/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFolder.java new file mode 100644 index 0000000000..e2762059bc --- /dev/null +++ b/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFolder.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (c) 2016 Sebastian Stenzel and others. + * This file is licensed under the terms of the MIT license. + * See the LICENSE.txt file for more info. + * + * Contributors: + * Sebastian Stenzel - initial API and implementation + *******************************************************************************/ +package org.cryptomator.filesystem.charsets; + +import java.io.UncheckedIOException; +import java.text.Normalizer; +import java.text.Normalizer.Form; + +import org.cryptomator.filesystem.File; +import org.cryptomator.filesystem.Folder; +import org.cryptomator.filesystem.delegating.DelegatingFolder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class NormalizedNameFolder extends DelegatingFolder { + + private static final Logger LOG = LoggerFactory.getLogger(NormalizedNameFolder.class); + private final Form displayForm; + + public NormalizedNameFolder(NormalizedNameFolder parent, Folder delegate, Form displayForm) { + super(parent, delegate); + this.displayForm = displayForm; + } + + @Override + public String name() throws UncheckedIOException { + return Normalizer.normalize(super.name(), displayForm); + } + + @Override + public NormalizedNameFile file(String name) throws UncheckedIOException { + String nfcName = Normalizer.normalize(name, Form.NFC); + String nfdName = Normalizer.normalize(name, Form.NFD); + NormalizedNameFile nfcFile = super.file(nfcName); + NormalizedNameFile nfdFile = super.file(nfdName); + if (!nfcName.equals(nfdName) && nfcFile.exists() && nfdFile.exists()) { + LOG.warn("Ambiguous file names \"" + nfcName + "\" (NFC) vs. \"" + nfdName + "\" (NFD). Both files exist. Using \"" + nfcName + "\" (NFC)."); + } else if (!nfcName.equals(nfdName) && !nfcFile.exists() && nfdFile.exists()) { + LOG.info("Moving file from \"" + nfcName + "\" (NFD) to \"" + nfdName + "\" (NFC)."); + nfdFile.moveTo(nfcFile); + } + return nfcFile; + } + + @Override + protected NormalizedNameFile newFile(File delegate) { + return new NormalizedNameFile(this, delegate, displayForm); + } + + @Override + public NormalizedNameFolder folder(String name) throws UncheckedIOException { + String nfcName = Normalizer.normalize(name, Form.NFC); + String nfdName = Normalizer.normalize(name, Form.NFD); + NormalizedNameFolder nfcFolder = super.folder(nfcName); + NormalizedNameFolder nfdFolder = super.folder(nfdName); + if (!nfcName.equals(nfdName) && nfcFolder.exists() && nfdFolder.exists()) { + LOG.warn("Ambiguous folder names \"" + nfcName + "\" (NFC) vs. \"" + nfdName + "\" (NFD). Both files exist. Using \"" + nfcName + "\" (NFC)."); + } else if (!nfcName.equals(nfdName) && !nfcFolder.exists() && nfdFolder.exists()) { + LOG.info("Moving folder from \"" + nfcName + "\" (NFD) to \"" + nfdName + "\" (NFC)."); + nfdFolder.moveTo(nfcFolder); + } + return nfcFolder; + } + + @Override + protected NormalizedNameFolder newFolder(Folder delegate) { + return new NormalizedNameFolder(this, delegate, displayForm); + } + +} diff --git a/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/package-info.java b/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/package-info.java new file mode 100644 index 0000000000..3ee4f6a178 --- /dev/null +++ b/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/package-info.java @@ -0,0 +1,16 @@ +/******************************************************************************* + * Copyright (c) 2016 Sebastian Stenzel and others. + * This file is licensed under the terms of the MIT license. + * See the LICENSE.txt file for more info. + * + * Contributors: + * Sebastian Stenzel - initial API and implementation + *******************************************************************************/ +/** + * Makes sure, the filesystems wrapped by this filesystem work only on UTF-8 encoded file paths using Normalization Form C. + * Filesystems wrapping this file system, on the other hand, will get filenames reported in a specified Normalization Form. + * It is recommended to use NFD for OS X and NFC for other operating systems. + * When looking for a file or folder with a name given in either form, both possibilities are considered + * and files/folders stored in NFD are automatically migrated to NFC. + */ +package org.cryptomator.filesystem.charsets; \ No newline at end of file diff --git a/main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFileSystemTest.java b/main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFileSystemTest.java new file mode 100644 index 0000000000..e662838a83 --- /dev/null +++ b/main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFileSystemTest.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2016 Sebastian Stenzel and others. + * This file is licensed under the terms of the MIT license. + * See the LICENSE.txt file for more info. + * + * Contributors: + * Sebastian Stenzel - initial API and implementation + *******************************************************************************/ +package org.cryptomator.filesystem.charsets; + +import java.nio.ByteBuffer; +import java.text.Normalizer.Form; + +import org.cryptomator.filesystem.FileSystem; +import org.cryptomator.filesystem.WritableFile; +import org.cryptomator.filesystem.inmem.InMemoryFileSystem; +import org.junit.Assert; +import org.junit.Test; + +public class NormalizedNameFileSystemTest { + + @Test + public void testFileMigration() { + FileSystem inMemoryFs = new InMemoryFileSystem(); + try (WritableFile writable = inMemoryFs.file("\u006F\u0302").openWritable()) { + writable.write(ByteBuffer.allocate(0)); + } + FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFC); + Assert.assertTrue(normalizationFs.file("\u00F4").exists()); + Assert.assertTrue(normalizationFs.file("\u006F\u0302").exists()); + Assert.assertFalse(inMemoryFs.file("\u006F\u0302").exists()); + Assert.assertTrue(inMemoryFs.file("\u00F4").exists()); + } + + @Test + public void testNoFileMigration() { + FileSystem inMemoryFs = new InMemoryFileSystem(); + try (WritableFile writable = inMemoryFs.file("\u00F4").openWritable()) { + writable.write(ByteBuffer.allocate(0)); + } + FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFC); + Assert.assertTrue(normalizationFs.file("\u00F4").exists()); + Assert.assertTrue(normalizationFs.file("\u006F\u0302").exists()); + Assert.assertFalse(inMemoryFs.file("\u006F\u0302").exists()); + Assert.assertTrue(inMemoryFs.file("\u00F4").exists()); + } + + @Test + public void testFolderMigration() { + FileSystem inMemoryFs = new InMemoryFileSystem(); + inMemoryFs.folder("\u006F\u0302").create(); + FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFC); + Assert.assertTrue(normalizationFs.folder("\u00F4").exists()); + Assert.assertTrue(normalizationFs.folder("\u006F\u0302").exists()); + Assert.assertFalse(inMemoryFs.folder("\u006F\u0302").exists()); + Assert.assertTrue(inMemoryFs.folder("\u00F4").exists()); + } + + @Test + public void testNoFolderMigration() { + FileSystem inMemoryFs = new InMemoryFileSystem(); + inMemoryFs.folder("\u00F4").create(); + FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFC); + Assert.assertTrue(normalizationFs.folder("\u00F4").exists()); + Assert.assertTrue(normalizationFs.folder("\u006F\u0302").exists()); + Assert.assertFalse(inMemoryFs.folder("\u006F\u0302").exists()); + Assert.assertTrue(inMemoryFs.folder("\u00F4").exists()); + } + + @Test + public void testNfcDisplayNames() { + FileSystem inMemoryFs = new InMemoryFileSystem(); + inMemoryFs.folder("a\u00F4").create(); + inMemoryFs.folder("b\u006F\u0302").create(); + FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFC); + Assert.assertEquals("a\u00F4", normalizationFs.folder("a\u00F4").name()); + Assert.assertEquals("b\u00F4", normalizationFs.folder("b\u006F\u0302").name()); + } + + @Test + public void testNfdDisplayNames() { + FileSystem inMemoryFs = new InMemoryFileSystem(); + inMemoryFs.folder("a\u00F4").create(); + inMemoryFs.folder("b\u006F\u0302").create(); + FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFD); + Assert.assertEquals("a\u006F\u0302", normalizationFs.folder("a\u00F4").name()); + Assert.assertEquals("b\u006F\u0302", normalizationFs.folder("b\u006F\u0302").name()); + } + +} diff --git a/main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFileTest.java b/main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFileTest.java new file mode 100644 index 0000000000..3a3f1fe643 --- /dev/null +++ b/main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFileTest.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2016 Sebastian Stenzel and others. + * This file is licensed under the terms of the MIT license. + * See the LICENSE.txt file for more info. + * + * Contributors: + * Sebastian Stenzel - initial API and implementation + *******************************************************************************/ +package org.cryptomator.filesystem.charsets; + +import java.text.Normalizer.Form; + +import org.cryptomator.filesystem.File; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +public class NormalizedNameFileTest { + + private File delegateNfc; + private File delegateNfd; + + @Before + public void setup() { + delegateNfc = Mockito.mock(File.class); + delegateNfd = Mockito.mock(File.class); + Mockito.when(delegateNfc.name()).thenReturn("\u00C5"); + Mockito.when(delegateNfd.name()).thenReturn("\u0041\u030A"); + } + + @Test + public void testDisplayNameInNfc() { + File file1 = new NormalizedNameFile(null, delegateNfc, Form.NFC); + File file2 = new NormalizedNameFile(null, delegateNfd, Form.NFC); + Assert.assertEquals("\u00C5", file1.name()); + Assert.assertEquals("\u00C5", file2.name()); + } + + @Test + public void testDisplayNameInNfd() { + File file1 = new NormalizedNameFile(null, delegateNfc, Form.NFD); + File file2 = new NormalizedNameFile(null, delegateNfd, Form.NFD); + Assert.assertEquals("\u0041\u030A", file1.name()); + Assert.assertEquals("\u0041\u030A", file2.name()); + } + +} diff --git a/main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFolderTest.java b/main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFolderTest.java new file mode 100644 index 0000000000..7bf0176528 --- /dev/null +++ b/main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFolderTest.java @@ -0,0 +1,149 @@ +/******************************************************************************* + * Copyright (c) 2016 Sebastian Stenzel and others. + * This file is licensed under the terms of the MIT license. + * See the LICENSE.txt file for more info. + * + * Contributors: + * Sebastian Stenzel - initial API and implementation + *******************************************************************************/ +package org.cryptomator.filesystem.charsets; + +import java.text.Normalizer.Form; + +import org.cryptomator.filesystem.File; +import org.cryptomator.filesystem.Folder; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +public class NormalizedNameFolderTest { + + private Folder delegate; + private File delegateSubFileNfc; + private File delegateSubFileNfd; + private Folder delegateSubFolderNfc; + private Folder delegateSubFolderNfd; + + @Before + public void setup() { + delegate = Mockito.mock(Folder.class); + delegateSubFileNfc = Mockito.mock(File.class); + delegateSubFileNfd = Mockito.mock(File.class); + Mockito.when(delegate.file("\u00C5")).thenReturn(delegateSubFileNfc); + Mockito.when(delegateSubFileNfc.name()).thenReturn("\u00C5"); + Mockito.when(delegate.file("\u0041\u030A")).thenReturn(delegateSubFileNfd); + Mockito.when(delegateSubFileNfd.name()).thenReturn("\u0041\u030A"); + delegateSubFolderNfc = Mockito.mock(Folder.class); + delegateSubFolderNfd = Mockito.mock(Folder.class); + Mockito.when(delegate.folder("\u00F4")).thenReturn(delegateSubFolderNfc); + Mockito.when(delegateSubFolderNfc.name()).thenReturn("\u00F4"); + Mockito.when(delegate.folder("\u006F\u0302")).thenReturn(delegateSubFolderNfd); + Mockito.when(delegateSubFolderNfd.name()).thenReturn("\u006F\u0302"); + } + + @Test + public void testDisplayNameInNfc() { + Folder folder1 = new NormalizedNameFolder(null, delegateSubFolderNfc, Form.NFC); + Folder folder2 = new NormalizedNameFolder(null, delegateSubFolderNfd, Form.NFC); + Assert.assertEquals("\u00F4", folder1.name()); + Assert.assertEquals("\u00F4", folder2.name()); + } + + @Test + public void testDisplayNameInNfd() { + Folder folder1 = new NormalizedNameFolder(null, delegateSubFolderNfc, Form.NFD); + Folder folder2 = new NormalizedNameFolder(null, delegateSubFolderNfd, Form.NFD); + Assert.assertEquals("\u006F\u0302", folder1.name()); + Assert.assertEquals("\u006F\u0302", folder2.name()); + } + + @Test + public void testNoFolderMigration1() { + Mockito.when(delegateSubFolderNfc.exists()).thenReturn(true); + Mockito.when(delegateSubFolderNfd.exists()).thenReturn(false); + Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC); + Folder sub1 = folder.folder("\u00F4"); + Folder sub2 = folder.folder("\u006F\u0302"); + Mockito.verify(delegateSubFolderNfd, Mockito.never()).moveTo(Mockito.any()); + Assert.assertSame(sub1, sub2); + } + + @Test + public void testNoFolderMigration2() { + Mockito.when(delegateSubFolderNfc.exists()).thenReturn(true); + Mockito.when(delegateSubFolderNfd.exists()).thenReturn(true); + Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC); + Folder sub1 = folder.folder("\u00F4"); + Folder sub2 = folder.folder("\u006F\u0302"); + Mockito.verify(delegateSubFolderNfd, Mockito.never()).moveTo(Mockito.any()); + Assert.assertSame(sub1, sub2); + } + + @Test + public void testNoFolderMigration3() { + Mockito.when(delegateSubFolderNfc.exists()).thenReturn(false); + Mockito.when(delegateSubFolderNfd.exists()).thenReturn(false); + Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC); + Folder sub1 = folder.folder("\u00F4"); + Folder sub2 = folder.folder("\u006F\u0302"); + Mockito.verify(delegateSubFolderNfd, Mockito.never()).moveTo(Mockito.any()); + Assert.assertSame(sub1, sub2); + } + + @Test + public void testFolderMigration() { + Mockito.when(delegateSubFolderNfc.exists()).thenReturn(false); + Mockito.when(delegateSubFolderNfd.exists()).thenReturn(true); + Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC); + Folder sub1 = folder.folder("\u00F4"); + Mockito.verify(delegateSubFolderNfd).moveTo(delegateSubFolderNfc); + Folder sub2 = folder.folder("\u006F\u0302"); + Assert.assertSame(sub1, sub2); + } + + @Test + public void testNoFileMigration1() { + Mockito.when(delegateSubFileNfc.exists()).thenReturn(true); + Mockito.when(delegateSubFileNfd.exists()).thenReturn(false); + Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC); + File sub1 = folder.file("\u00C5"); + File sub2 = folder.file("\u0041\u030A"); + Mockito.verify(delegateSubFileNfd, Mockito.never()).moveTo(Mockito.any()); + Assert.assertSame(sub1, sub2); + } + + @Test + public void testNoFileMigration2() { + Mockito.when(delegateSubFileNfc.exists()).thenReturn(true); + Mockito.when(delegateSubFileNfd.exists()).thenReturn(true); + Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC); + File sub1 = folder.file("\u00C5"); + File sub2 = folder.file("\u0041\u030A"); + Mockito.verify(delegateSubFileNfd, Mockito.never()).moveTo(Mockito.any()); + Assert.assertSame(sub1, sub2); + } + + @Test + public void testNoFileMigration3() { + Mockito.when(delegateSubFileNfc.exists()).thenReturn(false); + Mockito.when(delegateSubFileNfd.exists()).thenReturn(false); + Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC); + File sub1 = folder.file("\u00C5"); + File sub2 = folder.file("\u0041\u030A"); + Mockito.verify(delegateSubFileNfd, Mockito.never()).moveTo(Mockito.any()); + Assert.assertSame(sub1, sub2); + } + + @Test + public void testFileMigration() { + Mockito.when(delegateSubFileNfc.exists()).thenReturn(false); + Mockito.when(delegateSubFileNfd.exists()).thenReturn(true); + Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC); + File sub1 = folder.file("\u00C5"); + Mockito.verify(delegateSubFileNfd).moveTo(delegateSubFileNfc); + File sub2 = folder.file("\u0041\u030A"); + Assert.assertSame(sub1, sub2); + } + +} diff --git a/main/filesystem-charsets/src/test/resources/log4j2.xml b/main/filesystem-charsets/src/test/resources/log4j2.xml new file mode 100644 index 0000000000..9b48893929 --- /dev/null +++ b/main/filesystem-charsets/src/test/resources/log4j2.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/main/filesystem-invariants-tests/pom.xml b/main/filesystem-invariants-tests/pom.xml index aa13917ee5..0544643388 100644 --- a/main/filesystem-invariants-tests/pom.xml +++ b/main/filesystem-invariants-tests/pom.xml @@ -20,6 +20,10 @@ org.cryptomator filesystem-api + + org.cryptomator + filesystem-charsets + org.cryptomator filesystem-crypto diff --git a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FileSystemFactories.java b/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FileSystemFactories.java index 5f9a423f56..8f1eed8cad 100644 --- a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FileSystemFactories.java +++ b/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FileSystemFactories.java @@ -4,11 +4,13 @@ import java.io.IOException; import java.io.UncheckedIOException; +import java.text.Normalizer.Form; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.cryptomator.filesystem.FileSystem; +import org.cryptomator.filesystem.charsets.NormalizedNameFileSystem; import org.cryptomator.filesystem.crypto.CryptoEngineTestModule; import org.cryptomator.filesystem.crypto.CryptoFileSystemDelegate; import org.cryptomator.filesystem.crypto.CryptoFileSystemTestComponent; @@ -35,8 +37,10 @@ public FileSystemFactories() { add("ShorteningFileSystem > InMemoryFileSystem", this::createShorteningFileSystemInMemory); add("StatsFileSystem > NioFileSystem", this::createStatsFileSystemNio); add("StatsFileSystem > InMemoryFileSystem", this::createStatsFileSystemInMemory); - add("StatsFileSystem > CryptoFileSystem > ShorteningFileSystem > InMemoryFileSystem", this::createCompoundFileSystemInMemory); - add("StatsFileSystem > CryptoFileSystem > ShorteningFileSystem > NioFileSystem", this::createCompoundFileSystemNio); + add("NormalizingFileSystem > NioFileSystem", this::createNormalizingFileSystemNio); + add("NormalizingFileSystem > InMemoryFileSystem", this::createNormalizingFileSystemInMemory); + add("StatsFileSystem > NormalizingFileSystem > CryptoFileSystem > ShorteningFileSystem > InMemoryFileSystem", this::createCompoundFileSystemInMemory); + add("StatsFileSystem > NormalizingFileSystem > CryptoFileSystem > ShorteningFileSystem > NioFileSystem", this::createCompoundFileSystemNio); } private FileSystem createCryptoFileSystemInMemory() { @@ -63,6 +67,14 @@ private FileSystem createStatsFileSystemInMemory() { return createStatsFileSystem(createInMemoryFileSystem()); } + private FileSystem createNormalizingFileSystemNio() { + return createNormalizingFileSystem(createInMemoryFileSystem()); + } + + private FileSystem createNormalizingFileSystemInMemory() { + return createNormalizingFileSystem(createInMemoryFileSystem()); + } + private FileSystem createCompoundFileSystemNio() { return createCompoundFileSystem(createNioFileSystem()); } @@ -84,13 +96,17 @@ private FileSystem createInMemoryFileSystem() { } private FileSystem createCompoundFileSystem(FileSystem delegate) { - return createStatsFileSystem(createCryptoFileSystem(createShorteningFileSystem(delegate))); + return createStatsFileSystem(createNormalizingFileSystem(createCryptoFileSystem(createShorteningFileSystem(delegate)))); } private FileSystem createStatsFileSystem(FileSystem delegate) { return new StatsFileSystem(delegate); } + private FileSystem createNormalizingFileSystem(FileSystem delegate) { + return new NormalizedNameFileSystem(delegate, Form.NFC); + } + private FileSystem createCryptoFileSystem(FileSystem delegate) { CRYPTO_FS_COMP.cryptoFileSystemFactory().initializeNew(delegate, "aPassphrase"); return CRYPTO_FS_COMP.cryptoFileSystemFactory().unlockExisting(delegate, "aPassphrase", Mockito.mock(CryptoFileSystemDelegate.class)); diff --git a/main/pom.xml b/main/pom.xml index 1aac820791..75ba59663a 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -80,6 +80,11 @@ filesystem-api ${project.version} + + org.cryptomator + filesystem-charsets + ${project.version} + org.cryptomator filesystem-nio @@ -286,6 +291,7 @@ frontend-api frontend-webdav ui + filesystem-charsets diff --git a/main/ui/pom.xml b/main/ui/pom.xml index 6808f38879..873c1af386 100644 --- a/main/ui/pom.xml +++ b/main/ui/pom.xml @@ -38,6 +38,10 @@ org.cryptomator filesystem-crypto + + org.cryptomator + filesystem-charsets + org.cryptomator filesystem-stats diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java b/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java index 376e56d096..3cbc4bbc1f 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java @@ -30,6 +30,7 @@ import org.cryptomator.common.Optionals; import org.cryptomator.crypto.engine.InvalidPassphraseException; import org.cryptomator.filesystem.FileSystem; +import org.cryptomator.filesystem.charsets.NormalizedNameFileSystem; import org.cryptomator.filesystem.crypto.CryptoFileSystemDelegate; import org.cryptomator.filesystem.crypto.CryptoFileSystemFactory; import org.cryptomator.filesystem.nio.NioFileSystem; @@ -126,7 +127,8 @@ public synchronized void activateFrontend(FrontendFactory frontendFactory, Setti FileSystem fs = getNioFileSystem(); FileSystem shorteningFs = shorteningFileSystemFactory.get(fs); FileSystem cryptoFs = cryptoFileSystemFactory.unlockExisting(shorteningFs, passphrase, this); - StatsFileSystem statsFs = new StatsFileSystem(cryptoFs); + FileSystem normalizingFs = new NormalizedNameFileSystem(cryptoFs, SystemUtils.IS_OS_MAC_OSX ? Form.NFD : Form.NFC); + StatsFileSystem statsFs = new StatsFileSystem(normalizingFs); statsFileSystem = Optional.of(statsFs); String contextPath = StringUtils.prependIfMissing(mountName, "/"); Frontend frontend = frontendFactory.create(statsFs, contextPath);