From 79b825aaad6c35fd32b3106f0ba91e4ec8c8146e Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 16 Dec 2016 17:20:56 +0100 Subject: [PATCH 01/17] call me THE DESTROYER!!! first compile-clean but totally fubar version --- .../cryptomator/common/LazyInitializer.java | 52 +- main/filesystem-api/.gitignore | 2 - main/filesystem-api/pom.xml | 56 -- .../org/cryptomator/filesystem/Copier.java | 55 -- .../filesystem/DeadlockSafeFileOpener.java | 69 -- .../org/cryptomator/filesystem/Deleter.java | 15 - .../java/org/cryptomator/filesystem/File.java | 87 --- .../cryptomator/filesystem/FileSystem.java | 30 - .../org/cryptomator/filesystem/Folder.java | 145 ---- .../cryptomator/filesystem/FolderVisitor.java | 131 ---- .../java/org/cryptomator/filesystem/Node.java | 100 --- .../org/cryptomator/filesystem/OpenFiles.java | 71 -- .../cryptomator/filesystem/PathResolver.java | 112 --- .../cryptomator/filesystem/ReadableFile.java | 50 -- .../cryptomator/filesystem/WritableFile.java | 63 -- .../filesystem/delegating/DelegatingFile.java | 82 --- .../delegating/DelegatingFileSystem.java | 22 - .../delegating/DelegatingFolder.java | 100 --- .../filesystem/delegating/DelegatingNode.java | 78 -- .../delegating/DelegatingReadableFile.java | 44 -- .../delegating/DelegatingWritableFile.java | 49 -- .../cryptomator/filesystem/package-info.java | 13 - .../java/org/cryptomator/io/ByteBuffers.java | 35 - .../java/org/cryptomator/io/FileContents.java | 59 -- .../filesystem/ByteBufferMatcher.java | 36 - .../cryptomator/filesystem/CopierTest.java | 227 ------ .../filesystem/PathResolverTest.java | 70 -- .../delegating/DelegatingFileSystemTest.java | 32 - .../delegating/DelegatingFileTest.java | 186 ----- .../delegating/DelegatingFolderTest.java | 206 ------ .../DelegatingReadableFileTest.java | 64 -- .../DelegatingWritableFileTest.java | 74 -- .../delegating/TestDelegatingFile.java | 11 - .../delegating/TestDelegatingFileSystem.java | 20 - .../delegating/TestDelegatingFolder.java | 22 - .../org/cryptomator/io/ByteBuffersTest.java | 81 -- .../org/cryptomator/io/FileContentsTest.java | 103 --- .../src/test/resources/log4j2.xml | 22 - 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 - .../.gitignore | 1 - .../pom.xml | 64 -- .../crypto/CryptoEngineTestModule.java | 33 - .../crypto/CryptoFileSystemTestComponent.java | 32 - .../CryptoFileSystemIntegrationTest.java | 298 -------- .../src/test/resources/log4j2.xml | 22 - main/filesystem-crypto/.gitignore | 1 - main/filesystem-crypto/pom.xml | 93 --- .../engine/AuthenticationFailedException.java | 29 - .../crypto/engine/CryptoException.java | 29 - .../cryptomator/crypto/engine/Cryptor.java | 31 - .../crypto/engine/FileContentCryptor.java | 49 -- .../crypto/engine/FileContentDecryptor.java | 61 -- .../crypto/engine/FileContentEncryptor.java | 72 -- .../crypto/engine/FilenameCryptor.java | 44 -- .../engine/InvalidPassphraseException.java | 17 - .../UnsupportedVaultFormatException.java | 38 - .../crypto/engine/impl/AesKeyWrap.java | 71 -- .../crypto/engine/impl/Constants.java | 15 - .../engine/impl/CryptoEngineModule.java | 41 -- .../crypto/engine/impl/CryptorImpl.java | 197 ----- .../impl/FifoParallelDataProcessor.java | 70 -- .../engine/impl/FileContentCryptorImpl.java | 75 -- .../engine/impl/FileContentDecryptorImpl.java | 180 ----- .../engine/impl/FileContentEncryptorImpl.java | 187 ----- .../crypto/engine/impl/FileHeader.java | 117 --- .../crypto/engine/impl/FileHeaderPayload.java | 149 ---- .../engine/impl/FilenameCryptorImpl.java | 98 --- .../crypto/engine/impl/KeyFile.java | 100 --- .../crypto/engine/impl/Scrypt.java | 50 -- .../engine/impl/ThreadLocalAesCtrCipher.java | 36 - .../crypto/engine/impl/ThreadLocalMac.java | 46 -- .../crypto/engine/package-info.java | 12 - .../filesystem/crypto/BlockAlignedFile.java | 35 - .../crypto/BlockAlignedFileSystem.java | 61 -- .../crypto/BlockAlignedFileSystemFactory.java | 29 - .../filesystem/crypto/BlockAlignedFolder.java | 34 - .../crypto/BlockAlignedReadableFile.java | 108 --- .../crypto/BlockAlignedWritableFile.java | 136 ---- .../filesystem/crypto/CiphertextReader.java | 63 -- .../filesystem/crypto/CiphertextWriter.java | 51 -- .../filesystem/crypto/ConflictResolver.java | 105 --- .../filesystem/crypto/Constants.java | 16 - .../filesystem/crypto/CryptoFile.java | 116 --- .../filesystem/crypto/CryptoFileSystem.java | 108 --- .../crypto/CryptoFileSystemDelegate.java | 29 - .../crypto/CryptoFileSystemFactory.java | 61 -- .../filesystem/crypto/CryptoFolder.java | 243 ------ .../filesystem/crypto/CryptoNode.java | 122 --- .../filesystem/crypto/CryptoReadableFile.java | 111 --- .../filesystem/crypto/CryptoWritableFile.java | 113 --- .../filesystem/crypto/Masterkeys.java | 112 --- .../filesystem/crypto/package-info.java | 14 - .../cryptomator/crypto/engine/NoCryptor.java | 47 -- .../crypto/engine/NoFileContentCryptor.java | 135 ---- .../crypto/engine/NoFilenameCryptor.java | 67 -- .../crypto/engine/impl/CryptorImplTest.java | 100 --- .../impl/FifoParallelDataProcessorTest.java | 138 ---- .../impl/FileContentCryptorImplTest.java | 189 ----- .../impl/FileContentDecryptorImplTest.java | 195 ----- .../impl/FileContentEncryptorImplTest.java | 84 --- .../engine/impl/FileHeaderPayloadTest.java | 58 -- .../crypto/engine/impl/FileHeaderTest.java | 98 --- .../engine/impl/FilenameCryptorImplTest.java | 115 --- .../engine/impl/TestCryptorImplFactory.java | 36 - .../crypto/BlockAlignedReadableFileTest.java | 96 --- .../crypto/BlockAlignedWritableFileTest.java | 76 -- .../crypto/ConflictResolverTest.java | 161 ---- .../crypto/CryptoFileSystemTest.java | 229 ------ .../crypto/CryptoReadableFileTest.java | 48 -- .../filesystem/crypto/MasterkeysTest.java | 70 -- .../src/test/resources/log4j2.xml | 22 - main/filesystem-inmemory/.gitignore | 1 - main/filesystem-inmemory/pom.xml | 41 -- .../filesystem/inmem/InMemoryFile.java | 141 ---- .../filesystem/inmem/InMemoryFileSystem.java | 54 -- .../filesystem/inmem/InMemoryFolder.java | 121 --- .../filesystem/inmem/InMemoryNode.java | 96 --- .../inmem/InMemoryReadableFile.java | 68 -- .../inmem/InMemoryWritableFile.java | 102 --- .../inmem/InMemoryFileSystemTest.java | 168 ----- .../filesystem/inmem/InMemoryFileTest.java | 66 -- .../src/test/resources/log4j2.xml | 22 - main/filesystem-invariants-tests/.gitignore | 1 - main/filesystem-invariants-tests/pom.xml | 75 -- .../invariants/ConcurrencyTests.java | 134 ---- .../invariants/FileReadWriteTests.java | 133 ---- .../invariants/FileSystemFactories.java | 144 ---- .../invariants/FileSystemTests.java | 90 --- .../filesystem/invariants/FileTests.java | 204 ----- .../invariants/FolderChildrenTests.java | 229 ------ .../invariants/FolderCopyToTests.java | 143 ---- .../filesystem/invariants/FolderTests.java | 224 ------ .../invariants/WaysToObtainAFile.java | 135 ---- .../invariants/WaysToObtainAFolder.java | 122 --- .../invariants/matchers/InstantMatcher.java | 35 - .../invariants/matchers/NodeMatchers.java | 44 -- .../src/test/resources/log4j2.xml | 22 - main/filesystem-nameshortening/.gitignore | 1 - main/filesystem-nameshortening/pom.xml | 76 -- .../shortening/ConflictResolver.java | 70 -- .../shortening/FilenameShortener.java | 101 --- .../filesystem/shortening/ShorteningFile.java | 60 -- .../shortening/ShorteningFileSystem.java | 59 -- .../ShorteningFileSystemFactory.java | 30 - .../shortening/ShorteningFolder.java | 84 --- .../filesystem/shortening/package-info.java | 14 - .../shortening/ConflictResolverTest.java | 65 -- .../shortening/FilenameShortenerTest.java | 54 -- .../ShorteningFileSystemFactoryTest.java | 67 -- .../shortening/ShorteningFileSystemTest.java | 238 ------ .../ShorteningFileSystemTestComponent.java | 21 - .../src/test/resources/log4j2.xml | 22 - main/filesystem-nio/.gitignore | 1 - main/filesystem-nio/pom.xml | 64 -- .../nio/DefaultInstanceFactory.java | 35 - .../filesystem/nio/DefaultNioAccess.java | 106 --- .../filesystem/nio/InstanceFactory.java | 23 - .../cryptomator/filesystem/nio/NioAccess.java | 49 -- .../cryptomator/filesystem/nio/NioFile.java | 177 ----- .../filesystem/nio/NioFileSystem.java | 44 -- .../cryptomator/filesystem/nio/NioFolder.java | 142 ---- .../cryptomator/filesystem/nio/NioNode.java | 86 --- .../filesystem/nio/OpenCloseCounter.java | 25 - .../cryptomator/filesystem/nio/OpenMode.java | 5 - .../filesystem/nio/ReadableNioFile.java | 77 -- .../filesystem/nio/SharedFileChannel.java | 172 ----- .../filesystem/nio/WritableNioFile.java | 112 --- .../nio/DefaultInstanceFactoryTest.java | 92 --- .../filesystem/nio/DefaultNioAccessTest.java | 217 ------ .../filesystem/nio/InstanceFactoryTest.java | 18 - .../filesystem/nio/NioAccessTest.java | 18 - .../filesystem/nio/NioFileSystemTest.java | 60 -- .../filesystem/nio/NioFileTest.java | 517 ------------- .../filesystem/nio/NioFolderTest.java | 557 -------------- .../filesystem/nio/ReadableNioFileTest.java | 200 ----- .../nio/ReflectiveClassMatchers.java | 79 -- .../filesystem/nio/SampleFilesystem.java | 15 - .../filesystem/nio/SharedFileChannelTest.java | 696 ------------------ .../filesystem/nio/ThreadStackMatcher.java | 58 -- .../filesystem/nio/WritableNioFileTest.java | 289 -------- .../FilesystemSetupUtils.java | 112 --- .../NioFileIntegrationTest.java | 263 ------- .../NioFileSystemIntegrationTest.java | 44 -- .../NioFolderIntegrationTest.java | 347 --------- .../nio/integrationtests/NioNodeMatcher.java | 20 - .../nio/integrationtests/PathMatcher.java | 145 ---- .../src/test/resources/log4j2.xml | 22 - main/filesystem-stats/.gitignore | 1 - main/filesystem-stats/pom.xml | 45 -- .../filesystem/stats/StatsFile.java | 57 -- .../filesystem/stats/StatsFileSystem.java | 44 -- .../filesystem/stats/StatsFolder.java | 38 - .../filesystem/stats/StatsFileSystemTest.java | 50 -- .../filesystem/stats/StatsFileTest.java | 58 -- main/frontend-api/.gitignore | 1 - main/frontend-api/pom.xml | 27 - .../frontend/CommandFailedException.java | 24 - .../org/cryptomator/frontend/Frontend.java | 37 - .../FrontendCreationFailedException.java | 17 - .../cryptomator/frontend/FrontendFactory.java | 26 - .../org/cryptomator/frontend/FrontendId.java | 85 --- .../cryptomator/frontend/package-info.java | 12 - main/frontend-webdav/pom.xml | 134 ---- .../filesystem/jackrabbit/FileLocator.java | 55 -- .../jackrabbit/FileSystemLocator.java | 36 - .../jackrabbit/FileSystemResourceLocator.java | 58 -- .../FileSystemResourceLocatorFactory.java | 56 -- .../filesystem/jackrabbit/FolderLocator.java | 77 -- .../InternalFileSystemResourceLocator.java | 29 - .../frontend/webdav/ContextPathBuilder.java | 34 - .../frontend/webdav/DefaultServlet.java | 61 -- .../FontendIdHidingServletContextHandler.java | 17 - .../cryptomator/frontend/webdav/Tarpit.java | 71 -- .../frontend/webdav/WebDavFrontend.java | 71 -- .../frontend/webdav/WebDavModule.java | 11 - .../frontend/webdav/WebDavServer.java | 127 ---- .../webdav/WebDavServletContextFactory.java | 81 -- .../webdav/filters/AcceptRangeFilter.java | 46 -- .../frontend/webdav/filters/HttpFilter.java | 34 - .../webdav/filters/LoopbackFilter.java | 45 -- .../MacChunkedPutCompatibilityFilter.java | 154 ---- .../webdav/filters/MkcolComplianceFilter.java | 51 -- ...derRemovingHttpServletResponseWrapper.java | 43 -- .../filters/PostRequestBlockingFilter.java | 50 -- .../filters/UriNormalizationFilter.java | 196 ----- .../webdav/jackrabbitservlet/DavFile.java | 184 ----- .../jackrabbitservlet/DavFileWithRange.java | 91 --- .../DavFileWithUnsatisfiableRange.java | 50 -- .../webdav/jackrabbitservlet/DavFolder.java | 217 ------ .../webdav/jackrabbitservlet/DavNode.java | 253 ------- .../jackrabbitservlet/DavSessionImpl.java | 45 -- .../DavSessionProviderImpl.java | 36 - .../ExclusiveSharedLock.java | 109 --- .../ExclusiveSharedLockManager.java | 181 ----- .../FilesystemResourceFactory.java | 149 ---- .../jackrabbitservlet/NullInputContext.java | 53 -- .../UncheckedDavException.java | 30 - .../jackrabbitservlet/WebDavServlet.java | 168 ----- .../webdav/mount/AbstractWebDavMount.java | 18 - .../webdav/mount/FallbackWebDavMounter.java | 64 -- .../webdav/mount/LinuxGvfsDavMounter.java | 94 --- .../webdav/mount/LinuxGvfsWebDavMounter.java | 94 --- .../mount/MacOsXAppleScriptWebDavMounter.java | 123 ---- .../mount/MacOsXShellScriptWebDavMounter.java | 89 --- .../frontend/webdav/mount/WebDavMount.java | 34 - .../frontend/webdav/mount/WebDavMounter.java | 32 - .../webdav/mount/WebDavMounterModule.java | 31 - .../webdav/mount/WebDavMounterProvider.java | 44 -- .../webdav/mount/WebDavMounterStrategy.java | 35 - .../webdav/mount/WindowsWebDavMounter.java | 184 ----- .../webdav/mount/command/CommandResult.java | 108 --- .../webdav/mount/command/CommandRunner.java | 105 --- .../mount/command/FutureCommandResult.java | 115 --- .../frontend/webdav/mount/command/Script.java | 65 -- .../FileSystemResourceLocatorFactoryTest.java | 69 -- .../frontend/webdav/DefaultServletTest.java | 56 -- .../webdav/FileSystemWebDavServer.java | 52 -- .../frontend/webdav/InMemoryWebDavServer.java | 41 -- .../frontend/webdav/NioWebDavServer.java | 80 -- .../frontend/webdav/WebDavComponent.java | 21 - .../frontend/webdav/WebDavServerTest.java | 528 ------------- .../webdav/filters/LoggingHttpFilter.java | 182 ----- .../webdav/filters/LoopbackFilterTest.java | 65 -- .../MacChunkedPutCompatibilityFilterTest.java | 131 ---- .../filters/RecordingHttpServletRequest.java | 35 - .../filters/RecordingHttpServletResponse.java | 35 - .../filters/RecordingServletInputStream.java | 81 -- .../filters/RecordingServletOutputStream.java | 62 -- .../filters/UriNormalizationFilterTest.java | 201 ----- .../ExclusiveSharedLockManagerTest.java | 171 ----- .../src/test/resources/log4j2.xml | 33 - main/jacoco-report/pom.xml | 44 -- main/keychain/pom.xml | 1 - main/pom.xml | 90 +-- main/ui/pom.xml | 36 +- .../java/org/cryptomator/ui/Cryptomator.java | 10 +- .../cryptomator/ui/CryptomatorComponent.java | 14 +- .../org/cryptomator/ui/CryptomatorModule.java | 34 +- .../controllers/ChangePasswordController.java | 4 +- .../ui/controllers/MainController.java | 11 +- .../ui/controllers/UnlockController.java | 22 +- .../ui/controllers/UnlockedController.java | 21 +- .../ui/controllers/UpgradeController.java | 2 +- .../ui/controllers/WelcomeController.java | 18 +- .../ui/controls/DirectoryListCell.java | 2 +- .../cryptomator/ui/model/UpgradeStrategy.java | 15 +- .../UpgradeVersion3DropBundleExtension.java | 8 +- .../ui/model/UpgradeVersion3to4.java | 4 +- .../ui/model/UpgradeVersion4to5.java | 3 +- .../java/org/cryptomator/ui/model/Vault.java | 245 +++--- .../cryptomator/ui/model/VaultComponent.java | 13 + .../cryptomator/ui/model/VaultFactory.java | 30 +- .../org/cryptomator/ui/model/VaultList.java | 121 +++ .../org/cryptomator/ui/model/VaultModule.java | 36 + .../ui/model/VaultObjectMapperProvider.java | 97 --- .../java/org/cryptomator/ui/model/Vaults.java | 177 ----- .../ui/model}/WindowsDriveLetters.java | 24 +- .../cryptomator/ui/settings/Localization.java | 4 +- .../org/cryptomator/ui/settings/Settings.java | 42 +- .../ui/settings/SettingsInstanceCreator.java | 21 + .../ui/settings/SettingsProvider.java | 48 +- .../ui/settings/VaultSettings.java | 108 +++ .../ui/settings/VaultSettingsJsonAdapter.java | 64 ++ 311 files changed, 675 insertions(+), 25029 deletions(-) delete mode 100644 main/filesystem-api/.gitignore delete mode 100644 main/filesystem-api/pom.xml delete mode 100644 main/filesystem-api/src/main/java/org/cryptomator/filesystem/Copier.java delete mode 100644 main/filesystem-api/src/main/java/org/cryptomator/filesystem/DeadlockSafeFileOpener.java delete mode 100644 main/filesystem-api/src/main/java/org/cryptomator/filesystem/Deleter.java delete mode 100644 main/filesystem-api/src/main/java/org/cryptomator/filesystem/File.java delete mode 100644 main/filesystem-api/src/main/java/org/cryptomator/filesystem/FileSystem.java delete mode 100644 main/filesystem-api/src/main/java/org/cryptomator/filesystem/Folder.java delete mode 100644 main/filesystem-api/src/main/java/org/cryptomator/filesystem/FolderVisitor.java delete mode 100644 main/filesystem-api/src/main/java/org/cryptomator/filesystem/Node.java delete mode 100644 main/filesystem-api/src/main/java/org/cryptomator/filesystem/OpenFiles.java delete mode 100644 main/filesystem-api/src/main/java/org/cryptomator/filesystem/PathResolver.java delete mode 100644 main/filesystem-api/src/main/java/org/cryptomator/filesystem/ReadableFile.java delete mode 100644 main/filesystem-api/src/main/java/org/cryptomator/filesystem/WritableFile.java delete mode 100644 main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingFile.java delete mode 100644 main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingFileSystem.java delete mode 100644 main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingFolder.java delete mode 100644 main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingNode.java delete mode 100644 main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingReadableFile.java delete mode 100644 main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingWritableFile.java delete mode 100644 main/filesystem-api/src/main/java/org/cryptomator/filesystem/package-info.java delete mode 100644 main/filesystem-api/src/main/java/org/cryptomator/io/ByteBuffers.java delete mode 100644 main/filesystem-api/src/main/java/org/cryptomator/io/FileContents.java delete mode 100644 main/filesystem-api/src/test/java/org/cryptomator/filesystem/ByteBufferMatcher.java delete mode 100644 main/filesystem-api/src/test/java/org/cryptomator/filesystem/CopierTest.java delete mode 100644 main/filesystem-api/src/test/java/org/cryptomator/filesystem/PathResolverTest.java delete mode 100644 main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingFileSystemTest.java delete mode 100644 main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingFileTest.java delete mode 100644 main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingFolderTest.java delete mode 100644 main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingReadableFileTest.java delete mode 100644 main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingWritableFileTest.java delete mode 100644 main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/TestDelegatingFile.java delete mode 100644 main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/TestDelegatingFileSystem.java delete mode 100644 main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/TestDelegatingFolder.java delete mode 100644 main/filesystem-api/src/test/java/org/cryptomator/io/ByteBuffersTest.java delete mode 100644 main/filesystem-api/src/test/java/org/cryptomator/io/FileContentsTest.java delete mode 100644 main/filesystem-api/src/test/resources/log4j2.xml delete mode 100644 main/filesystem-charsets/pom.xml delete mode 100644 main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFile.java delete mode 100644 main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFileSystem.java delete mode 100644 main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFolder.java delete mode 100644 main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/package-info.java delete mode 100644 main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFileSystemTest.java delete mode 100644 main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFileTest.java delete mode 100644 main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFolderTest.java delete mode 100644 main/filesystem-charsets/src/test/resources/log4j2.xml delete mode 100644 main/filesystem-crypto-integration-tests/.gitignore delete mode 100644 main/filesystem-crypto-integration-tests/pom.xml delete mode 100644 main/filesystem-crypto-integration-tests/src/main/java/org/cryptomator/filesystem/crypto/CryptoEngineTestModule.java delete mode 100644 main/filesystem-crypto-integration-tests/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystemTestComponent.java delete mode 100644 main/filesystem-crypto-integration-tests/src/test/java/org/cryptomator/filesystem/crypto/CryptoFileSystemIntegrationTest.java delete mode 100644 main/filesystem-crypto-integration-tests/src/test/resources/log4j2.xml delete mode 100644 main/filesystem-crypto/.gitignore delete mode 100644 main/filesystem-crypto/pom.xml delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/AuthenticationFailedException.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/CryptoException.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/Cryptor.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentCryptor.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentDecryptor.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentEncryptor.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FilenameCryptor.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/InvalidPassphraseException.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/UnsupportedVaultFormatException.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/AesKeyWrap.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Constants.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/CryptoEngineModule.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/CryptorImpl.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FifoParallelDataProcessor.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentCryptorImpl.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImpl.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImpl.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileHeader.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileHeaderPayload.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImpl.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/KeyFile.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Scrypt.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/ThreadLocalAesCtrCipher.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/ThreadLocalMac.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/package-info.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedFile.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedFileSystem.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedFileSystemFactory.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedFolder.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedReadableFile.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedWritableFile.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CiphertextReader.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CiphertextWriter.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/ConflictResolver.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/Constants.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFile.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystem.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystemDelegate.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystemFactory.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFolder.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoNode.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoReadableFile.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoWritableFile.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/Masterkeys.java delete mode 100644 main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/package-info.java delete mode 100644 main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/NoCryptor.java delete mode 100644 main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/NoFileContentCryptor.java delete mode 100644 main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/NoFilenameCryptor.java delete mode 100644 main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/CryptorImplTest.java delete mode 100644 main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FifoParallelDataProcessorTest.java delete mode 100644 main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentCryptorImplTest.java delete mode 100644 main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImplTest.java delete mode 100644 main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImplTest.java delete mode 100644 main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileHeaderPayloadTest.java delete mode 100644 main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileHeaderTest.java delete mode 100644 main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImplTest.java delete mode 100644 main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/TestCryptorImplFactory.java delete mode 100644 main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/BlockAlignedReadableFileTest.java delete mode 100644 main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/BlockAlignedWritableFileTest.java delete mode 100644 main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/ConflictResolverTest.java delete mode 100644 main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/CryptoFileSystemTest.java delete mode 100644 main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/CryptoReadableFileTest.java delete mode 100644 main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/MasterkeysTest.java delete mode 100644 main/filesystem-crypto/src/test/resources/log4j2.xml delete mode 100644 main/filesystem-inmemory/.gitignore delete mode 100644 main/filesystem-inmemory/pom.xml delete mode 100644 main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFile.java delete mode 100644 main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFileSystem.java delete mode 100644 main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFolder.java delete mode 100644 main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryNode.java delete mode 100644 main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryReadableFile.java delete mode 100644 main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryWritableFile.java delete mode 100644 main/filesystem-inmemory/src/test/java/org/cryptomator/filesystem/inmem/InMemoryFileSystemTest.java delete mode 100644 main/filesystem-inmemory/src/test/java/org/cryptomator/filesystem/inmem/InMemoryFileTest.java delete mode 100644 main/filesystem-inmemory/src/test/resources/log4j2.xml delete mode 100644 main/filesystem-invariants-tests/.gitignore delete mode 100644 main/filesystem-invariants-tests/pom.xml delete mode 100644 main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/ConcurrencyTests.java delete mode 100644 main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FileReadWriteTests.java delete mode 100644 main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FileSystemFactories.java delete mode 100644 main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FileSystemTests.java delete mode 100644 main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FileTests.java delete mode 100644 main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FolderChildrenTests.java delete mode 100644 main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FolderCopyToTests.java delete mode 100644 main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FolderTests.java delete mode 100644 main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/WaysToObtainAFile.java delete mode 100644 main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/WaysToObtainAFolder.java delete mode 100644 main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/matchers/InstantMatcher.java delete mode 100644 main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/matchers/NodeMatchers.java delete mode 100644 main/filesystem-invariants-tests/src/test/resources/log4j2.xml delete mode 100644 main/filesystem-nameshortening/.gitignore delete mode 100644 main/filesystem-nameshortening/pom.xml delete mode 100644 main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/ConflictResolver.java delete mode 100644 main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/FilenameShortener.java delete mode 100644 main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/ShorteningFile.java delete mode 100644 main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/ShorteningFileSystem.java delete mode 100644 main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/ShorteningFileSystemFactory.java delete mode 100644 main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/ShorteningFolder.java delete mode 100644 main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/package-info.java delete mode 100644 main/filesystem-nameshortening/src/test/java/org/cryptomator/filesystem/shortening/ConflictResolverTest.java delete mode 100644 main/filesystem-nameshortening/src/test/java/org/cryptomator/filesystem/shortening/FilenameShortenerTest.java delete mode 100644 main/filesystem-nameshortening/src/test/java/org/cryptomator/filesystem/shortening/ShorteningFileSystemFactoryTest.java delete mode 100644 main/filesystem-nameshortening/src/test/java/org/cryptomator/filesystem/shortening/ShorteningFileSystemTest.java delete mode 100644 main/filesystem-nameshortening/src/test/java/org/cryptomator/filesystem/shortening/ShorteningFileSystemTestComponent.java delete mode 100644 main/filesystem-nameshortening/src/test/resources/log4j2.xml delete mode 100644 main/filesystem-nio/.gitignore delete mode 100644 main/filesystem-nio/pom.xml delete mode 100644 main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/DefaultInstanceFactory.java delete mode 100644 main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/DefaultNioAccess.java delete mode 100644 main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/InstanceFactory.java delete mode 100644 main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioAccess.java delete mode 100644 main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFile.java delete mode 100644 main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFileSystem.java delete mode 100644 main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFolder.java delete mode 100644 main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioNode.java delete mode 100644 main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/OpenCloseCounter.java delete mode 100644 main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/OpenMode.java delete mode 100644 main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/ReadableNioFile.java delete mode 100644 main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/SharedFileChannel.java delete mode 100644 main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/WritableNioFile.java delete mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/DefaultInstanceFactoryTest.java delete mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/DefaultNioAccessTest.java delete mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/InstanceFactoryTest.java delete mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioAccessTest.java delete mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileSystemTest.java delete mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileTest.java delete mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFolderTest.java delete mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/ReadableNioFileTest.java delete mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/ReflectiveClassMatchers.java delete mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/SampleFilesystem.java delete mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/SharedFileChannelTest.java delete mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/ThreadStackMatcher.java delete mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/WritableNioFileTest.java delete mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/FilesystemSetupUtils.java delete mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioFileIntegrationTest.java delete mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioFileSystemIntegrationTest.java delete mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioFolderIntegrationTest.java delete mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioNodeMatcher.java delete mode 100644 main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/PathMatcher.java delete mode 100644 main/filesystem-nio/src/test/resources/log4j2.xml delete mode 100644 main/filesystem-stats/.gitignore delete mode 100644 main/filesystem-stats/pom.xml delete mode 100644 main/filesystem-stats/src/main/java/org/cryptomator/filesystem/stats/StatsFile.java delete mode 100644 main/filesystem-stats/src/main/java/org/cryptomator/filesystem/stats/StatsFileSystem.java delete mode 100644 main/filesystem-stats/src/main/java/org/cryptomator/filesystem/stats/StatsFolder.java delete mode 100644 main/filesystem-stats/src/test/java/org/cryptomator/filesystem/stats/StatsFileSystemTest.java delete mode 100644 main/filesystem-stats/src/test/java/org/cryptomator/filesystem/stats/StatsFileTest.java delete mode 100644 main/frontend-api/.gitignore delete mode 100644 main/frontend-api/pom.xml delete mode 100644 main/frontend-api/src/main/java/org/cryptomator/frontend/CommandFailedException.java delete mode 100644 main/frontend-api/src/main/java/org/cryptomator/frontend/Frontend.java delete mode 100644 main/frontend-api/src/main/java/org/cryptomator/frontend/FrontendCreationFailedException.java delete mode 100644 main/frontend-api/src/main/java/org/cryptomator/frontend/FrontendFactory.java delete mode 100644 main/frontend-api/src/main/java/org/cryptomator/frontend/FrontendId.java delete mode 100644 main/frontend-api/src/main/java/org/cryptomator/frontend/package-info.java delete mode 100644 main/frontend-webdav/pom.xml delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FileLocator.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FileSystemLocator.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FileSystemResourceLocator.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FileSystemResourceLocatorFactory.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FolderLocator.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/InternalFileSystemResourceLocator.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/ContextPathBuilder.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/DefaultServlet.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/FontendIdHidingServletContextHandler.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/Tarpit.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavFrontend.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavModule.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavServer.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavServletContextFactory.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/AcceptRangeFilter.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/HttpFilter.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/LoopbackFilter.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/MacChunkedPutCompatibilityFilter.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/MkcolComplianceFilter.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/PostFromAllowHeaderRemovingHttpServletResponseWrapper.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/PostRequestBlockingFilter.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/UriNormalizationFilter.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/DavFile.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/DavFileWithRange.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/DavFileWithUnsatisfiableRange.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/DavFolder.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/DavNode.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/DavSessionImpl.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/DavSessionProviderImpl.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/ExclusiveSharedLock.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/ExclusiveSharedLockManager.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/FilesystemResourceFactory.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/NullInputContext.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/UncheckedDavException.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/WebDavServlet.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/AbstractWebDavMount.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/FallbackWebDavMounter.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/LinuxGvfsDavMounter.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/LinuxGvfsWebDavMounter.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/MacOsXAppleScriptWebDavMounter.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/MacOsXShellScriptWebDavMounter.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WebDavMount.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WebDavMounter.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WebDavMounterModule.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WebDavMounterProvider.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WebDavMounterStrategy.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WindowsWebDavMounter.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/command/CommandResult.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/command/CommandRunner.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/command/FutureCommandResult.java delete mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/command/Script.java delete mode 100644 main/frontend-webdav/src/test/java/org/cryptomator/filesystem/jackrabbit/FileSystemResourceLocatorFactoryTest.java delete mode 100644 main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/DefaultServletTest.java delete mode 100644 main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/FileSystemWebDavServer.java delete mode 100644 main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/InMemoryWebDavServer.java delete mode 100644 main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/NioWebDavServer.java delete mode 100644 main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/WebDavComponent.java delete mode 100644 main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/WebDavServerTest.java delete mode 100644 main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/LoggingHttpFilter.java delete mode 100644 main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/LoopbackFilterTest.java delete mode 100644 main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/MacChunkedPutCompatibilityFilterTest.java delete mode 100644 main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/RecordingHttpServletRequest.java delete mode 100644 main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/RecordingHttpServletResponse.java delete mode 100644 main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/RecordingServletInputStream.java delete mode 100644 main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/RecordingServletOutputStream.java delete mode 100644 main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/UriNormalizationFilterTest.java delete mode 100644 main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/jackrabbitservlet/ExclusiveSharedLockManagerTest.java delete mode 100644 main/frontend-webdav/src/test/resources/log4j2.xml create mode 100644 main/ui/src/main/java/org/cryptomator/ui/model/VaultComponent.java create mode 100644 main/ui/src/main/java/org/cryptomator/ui/model/VaultList.java create mode 100644 main/ui/src/main/java/org/cryptomator/ui/model/VaultModule.java delete mode 100644 main/ui/src/main/java/org/cryptomator/ui/model/VaultObjectMapperProvider.java delete mode 100644 main/ui/src/main/java/org/cryptomator/ui/model/Vaults.java rename main/{frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount => ui/src/main/java/org/cryptomator/ui/model}/WindowsDriveLetters.java (59%) create mode 100644 main/ui/src/main/java/org/cryptomator/ui/settings/SettingsInstanceCreator.java create mode 100644 main/ui/src/main/java/org/cryptomator/ui/settings/VaultSettings.java create mode 100644 main/ui/src/main/java/org/cryptomator/ui/settings/VaultSettingsJsonAdapter.java diff --git a/main/commons/src/main/java/org/cryptomator/common/LazyInitializer.java b/main/commons/src/main/java/org/cryptomator/common/LazyInitializer.java index 1316c50d1..62f75c8e8 100644 --- a/main/commons/src/main/java/org/cryptomator/common/LazyInitializer.java +++ b/main/commons/src/main/java/org/cryptomator/common/LazyInitializer.java @@ -2,6 +2,7 @@ package org.cryptomator.common; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; +import java.util.function.UnaryOperator; public final class LazyInitializer { @@ -9,7 +10,7 @@ public final class LazyInitializer { } /** - * Threadsafe lazy initialization pattern as proposed on http://stackoverflow.com/a/30247202/4014509 + * Same as {@link #initializeLazily(AtomicReference, SupplierThrowingException, Class)} except that no checked exception may be thrown by the factory function. * * @param Type of the value * @param reference A reference to a maybe not yet initialized value. @@ -17,18 +18,57 @@ public final class LazyInitializer { * @return The initialized value */ public static T initializeLazily(AtomicReference reference, Supplier factory) { + SupplierThrowingException factoryThrowingRuntimeExceptions = () -> factory.get(); + return initializeLazily(reference, factoryThrowingRuntimeExceptions, RuntimeException.class); + } + + /** + * Threadsafe lazy initialization pattern as proposed on http://stackoverflow.com/a/30247202/4014509 + * + * @param Type of the value + * @param Type of the any expected exception that may occur during initialization + * @param reference A reference to a maybe not yet initialized value. + * @param factory A factory providing a value for the reference, if it doesn't exist yet. The factory may be invoked multiple times, but only one result will survive. + * @param exceptionType Expected exception type. + * @return The initialized value + * @throws E Exception thrown by the factory function. + */ + public static T initializeLazily(AtomicReference reference, SupplierThrowingException factory, Class exceptionType) throws E { final T existing = reference.get(); if (existing != null) { return existing; } else { - return reference.updateAndGet(currentValue -> { - if (currentValue == null) { - return factory.get(); + try { + return reference.updateAndGet(invokeFactoryIfNull(factory)); + } catch (InitializationException e) { + if (exceptionType.isInstance(e.getCause())) { + throw exceptionType.cast(e.getCause()); } else { - return currentValue; + throw e; } - }); + } } } + private static UnaryOperator invokeFactoryIfNull(SupplierThrowingException factory) throws InitializationException { + return currentValue -> { + if (currentValue == null) { + try { + return factory.get(); + } catch (Throwable e) { + throw new InitializationException(e); + } + } else { + return currentValue; + } + }; + } + + private static class InitializationException extends RuntimeException { + + public InitializationException(Throwable cause) { + super(cause); + } + + } } diff --git a/main/filesystem-api/.gitignore b/main/filesystem-api/.gitignore deleted file mode 100644 index 1dd333108..000000000 --- a/main/filesystem-api/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/target/ -/target/ diff --git a/main/filesystem-api/pom.xml b/main/filesystem-api/pom.xml deleted file mode 100644 index a815393c7..000000000 --- a/main/filesystem-api/pom.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - 4.0.0 - - org.cryptomator - main - 1.3.0-SNAPSHOT - - filesystem-api - Cryptomator filesystem: API - - - - org.cryptomator - commons - - - - - com.google.guava - guava - - - - - commons-io - commons-io - - - org.apache.commons - commons-lang3 - - - org.apache.commons - commons-collections4 - - - org.cryptomator - commons-test - - - - - - - org.jacoco - jacoco-maven-plugin - - - - \ No newline at end of file diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Copier.java b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Copier.java deleted file mode 100644 index b536485f3..000000000 --- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Copier.java +++ /dev/null @@ -1,55 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 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; - -import java.io.IOException; -import java.io.UncheckedIOException; - -import com.google.common.io.ByteStreams; - -class Copier { - - public static void copy(Folder source, Folder destination) { - assertFoldersAreNotNested(source, destination); - - destination.delete(); - destination.create(); - - source.files().forEach(sourceFile -> { - File destinationFile = destination.file(sourceFile.name()); - sourceFile.copyTo(destinationFile); - }); - - source.folders().forEach(sourceFolder -> { - Folder destinationFolder = destination.folder(sourceFolder.name()); - sourceFolder.copyTo(destinationFolder); - }); - } - - public static void copy(File source, File destination) { - try (OpenFiles openFiles = DeadlockSafeFileOpener.withReadable(source).andWritable(destination).open()) { - ReadableFile readable = openFiles.readable(source); - WritableFile writable = openFiles.writable(destination); - writable.truncate(); - ByteStreams.copy(readable, writable); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - private static void assertFoldersAreNotNested(Folder source, Folder destination) { - if (source.isAncestorOf(destination)) { - throw new IllegalArgumentException("Can not copy parent to child directory (src: " + source + ", dst: " + destination + ")"); - } - if (destination.isAncestorOf(source)) { - throw new IllegalArgumentException("Can not copy child to parent directory (src: " + source + ", dst: " + destination + ")"); - } - } - -} diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/DeadlockSafeFileOpener.java b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/DeadlockSafeFileOpener.java deleted file mode 100644 index eaccbee43..000000000 --- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/DeadlockSafeFileOpener.java +++ /dev/null @@ -1,69 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 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; - -import static java.lang.String.format; - -import java.util.HashMap; -import java.util.Map; -import java.util.SortedMap; -import java.util.TreeMap; -import java.util.function.Consumer; - -public class DeadlockSafeFileOpener { - - public static DeadlockSafeFileOpener withReadable(File file) { - return new DeadlockSafeFileOpener().andReadable(file); - } - - public static DeadlockSafeFileOpener withWritable(File file) { - return new DeadlockSafeFileOpener().andWritable(file); - } - - private final SortedMap> filesWithOperation = new TreeMap<>(); - - private final Map readableFiles = new HashMap<>(); - private final Map writableFiles = new HashMap<>(); - - private DeadlockSafeFileOpener() { - } - - public DeadlockSafeFileOpener andReadable(File file) { - if (filesWithOperation.put(file, this::openReadable) != null) { - throw new IllegalArgumentException(format("File %s already marked for opening", file)); - } - return this; - } - - public DeadlockSafeFileOpener andWritable(File file) { - if (filesWithOperation.put(file, this::openWritable) != null) { - throw new IllegalArgumentException(format("File %s already marked for opening", file)); - } - return this; - } - - private void openReadable(File file) { - readableFiles.put(file, file.openReadable()); - } - - private void openWritable(File file) { - writableFiles.put(file, file.openWritable()); - } - - public OpenFiles open() { - try { - filesWithOperation.forEach((file, openAction) -> openAction.accept(file)); - } catch (RuntimeException e) { - OpenFiles.cleanup(readableFiles.values(), writableFiles.values()); - throw e; - } - return new OpenFiles(readableFiles, writableFiles); - } - -} diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Deleter.java b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Deleter.java deleted file mode 100644 index 2ae71d3f5..000000000 --- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Deleter.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.cryptomator.filesystem; - -public class Deleter { - - /** - * Deletes all and only the content of a given {@link Folder} but not the folder itself. - */ - public static void deleteContent(Folder folder) { - if (folder.exists()) { - folder.folders().forEach(Folder::delete); - folder.files().forEach(File::delete); - } - } - -} diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/File.java b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/File.java deleted file mode 100644 index e973c7255..000000000 --- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/File.java +++ /dev/null @@ -1,87 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Markus Kreusch - * This file is licensed under the terms of the MIT license. - * See the LICENSE.txt file for more info. - ******************************************************************************/ -package org.cryptomator.filesystem; - -import java.io.IOException; -import java.io.UncheckedIOException; - -/** - * A {@link File} in a {@link FileSystem}. - * - * @author Markus Kreusch - */ -public interface File extends Node, Comparable { - - static final int EOF = -1; - - /** - * @return The current size of the file. This value is a snapshot and might have been changed by concurrent modifications. - * @throws UncheckedIOException - * if an {@link IOException} occurs - */ - long size() throws UncheckedIOException; - - /** - *

- * Opens this file for reading. - *

- * An implementation guarantees, that per {@link FileSystem} and - * {@code File} while a {@code ReadableFile} is open no {@link WritableFile} - * can be open and vice versa. A {@link ReadableFile} is open when returned - * from this method and not yet closed using {@link ReadableFile#close()}. - *
- * A limitation to the number of {@code ReadableFiles} is in general not - * required but may be set by a specific implementation. - *

- * If a {@link WritableFile} for this {@code File} is open the invocation of - * this method will block regarding the specified timeout.
- * In addition implementations may block to lock the required IO resources - * to read the file. - * - * @return a {@link ReadableFile} to work with - * @throws UncheckedIOException - * if an {@link IOException} occurs while opening the file, the - * file does not exist or is a directory - */ - ReadableFile openReadable() throws UncheckedIOException; - - /** - *

- * Opens this file for writing. - *

- * If the file does not exist a new empty file is created. - *

- * An implementation guarantees, that per {@link FileSystem} and - * {@code File} only one {@link WritableFile} is open at a time. A - * {@code WritableFile} is open when returned from this method and not yet - * closed using {@link WritableFile#close()} or - * {@link WritableFile#delete()}.
- * In addition while a {@code WritableFile} is open no {@link ReadableFile} - * can be open and vice versa. - *

- * If a {@code Readable-} or {@code WritableFile} for this {@code File} is - * open the invocation of this method will block regarding the specified - * timeout.
- * In addition implementations may block to lock the required IO resources - * to read the file. - * - * @return a {@link WritableFile} to work with - * @throws UncheckedIOException - * if an {@link IOException} occurs while opening the file or - * the file is a directory - */ - WritableFile openWritable() throws UncheckedIOException; - - default void copyTo(File destination) { - Copier.copy(this, destination); - } - - /** - * Moves this file including content to a new location specified by destination. - */ - void moveTo(File destination) throws UncheckedIOException; - -} diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/FileSystem.java b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/FileSystem.java deleted file mode 100644 index 138fcb95e..000000000 --- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/FileSystem.java +++ /dev/null @@ -1,30 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Markus Kreusch - * This file is licensed under the terms of the MIT license. - * See the LICENSE.txt file for more info. - ******************************************************************************/ -package org.cryptomator.filesystem; - -import java.util.Optional; - -/** - * The root folder of a file system. - * - * @author Markus Kreusch - */ -public interface FileSystem extends Folder { - - /** - * @return an empty {@link Optional} because a {@link FileSystem} represents - * the root {@link Folder} and thus does not have a parent - */ - @Override - default Optional parent() { - return Optional.empty(); - } - - Optional quotaUsedBytes(); - - Optional quotaAvailableBytes(); - -} diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Folder.java b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Folder.java deleted file mode 100644 index 3bf62065f..000000000 --- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Folder.java +++ /dev/null @@ -1,145 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Markus Kreusch - * This file is licensed under the terms of the MIT license. - * See the LICENSE.txt file for more info. - ******************************************************************************/ -package org.cryptomator.filesystem; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.util.stream.Stream; - -/** - * A {@link Folder} in a {@link FileSystem}. - * - * @author Markus Kreusch - */ -public interface Folder extends Node { - - /** - *

- * Creates a {@link Stream} over all child nodes of this {@code Folder}. - *

- * Note: The {@link Stream} may be lazily populated and thus - * {@link IOException IOExceptions} may occurs after this method returned. - * In this case implementors should throw a {@link UncheckedIOException} - * from any method that produces an {@link IOException}. Thus users should - * expect {@link UncheckedIOException UncheckedIOExceptions} when invoking - * methods on the returned {@code Stream}. - * - * @return the created {@code Stream} - * @throws UncheckedIOException - * if an {@link IOException} occurs while initializing the - * stream or the {@code Folder} does not exist - */ - Stream children() throws UncheckedIOException; - - /** - *

- * Returns the child {@link Node} in this directory of type {@link File} - * with the specified name. - *

- * This operation always returns a {@link File} without checking if the file - * exists or is a {@link Folder} instead. - */ - File file(String name) throws UncheckedIOException; - - /** - * Returns a file by resolving a path relative to this folder. - * - * @param path A unix-style path, which is always relative to this folder, no matter if it starts with a slash or not. Path must not be empty. - * @return File with the given path relative to this folder - * @throws IllegalArgumentException - * if relativePath is empty - */ - default File resolveFile(String relativePath) throws UncheckedIOException, IllegalArgumentException { - return PathResolver.resolveFile(this, relativePath); - } - - /** - *

- * Returns the child {@link Node} in this directory of type {@link Folder} - * with the specified name. - *

- * This operation always returns a {@link Folder} without checking if the - * folder exists or is a {@link File} instead. - */ - Folder folder(String name) throws UncheckedIOException; - - /** - * Returns a folder by resolving a path relative to this folder. - * - * @param path A unix-style path, which is always relative to this folder, no matter if it starts with a slash or not. Path may be empty. - * @return Folder with the given path relative to this folder. Returns this if path is empty. - */ - default Folder resolveFolder(String relativePath) throws UncheckedIOException { - return PathResolver.resolveFolder(this, relativePath); - } - - /** - * Creates the directory including all parent directories, if it doesn't - * exist yet. No effect, if folder already exists. - * - * @throws UncheckedIOException - * if an {@link IOException} occurs while creating the folder or - * one of its parents - */ - void create() throws UncheckedIOException; - - /** - * Recusively copies this directory and all its contents to (not into) the - * given destination, creating nonexisting parent directories. If the target - * exists it is deleted before performing the copy. - * - * @param target - * Destination folder. Must not be a descendant of this folder. - */ - default void copyTo(Folder target) throws UncheckedIOException { - Copier.copy(this, target); - } - - /** - * Moves this directory and its contents to the given destination. If the - * target exists it is deleted before performing the move. - */ - void moveTo(Folder target); - - /** - * @return the result of {@link #children()} filtered to contain only - * {@link File Files} - */ - default Stream files() throws UncheckedIOException { - return children() // - .filter(File.class::isInstance) // - .map(File.class::cast); - } - - /** - * @return the result of {@link #children()} filtered to contain only - * {@link Folder Folders} - */ - default Stream folders() throws UncheckedIOException { - return children() // - .filter(Folder.class::isInstance) // - .map(Folder.class::cast); - } - - /** - * Recursively checks whether this folder or any subfolder contains the - * given node. - * - * @param node - * Potential child, grandchild, ... - * @return true if this folder is an ancestor of the node. - */ - default boolean isAncestorOf(Node node) { - if (!node.parent().isPresent()) { - return false; - } else if (node.parent().get().equals(this)) { - return true; - } else { - return this.isAncestorOf(node.parent().get()); - } - } - -} diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/FolderVisitor.java b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/FolderVisitor.java deleted file mode 100644 index c46de4a17..000000000 --- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/FolderVisitor.java +++ /dev/null @@ -1,131 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 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; - -import static java.lang.String.format; - -import java.util.function.Consumer; - -public class FolderVisitor { - - private final Consumer beforeFolderVisitor; - private final Consumer afterFolderVisitor; - private final Consumer fileVisitor; - private final Consumer nodeVisitor; - private final int maxDepth; - - public FolderVisitor(FolderVisitorBuilder builder) { - this.beforeFolderVisitor = builder.beforeFolderVisitor; - this.afterFolderVisitor = builder.afterFolderVisitor; - this.fileVisitor = builder.fileVisitor; - this.nodeVisitor = builder.nodeVisitor; - this.maxDepth = builder.maxDepth; - } - - public static FolderVisitorBuilder folderVisitor() { - return new FolderVisitorBuilder(); - } - - public FolderVisitor visit(Folder folder) { - return visit(folder, 0); - } - - public FolderVisitor visit(File file) { - return visit(file, 0); - } - - private FolderVisitor visit(Folder folder, int depth) { - beforeFolderVisitor.accept(folder); - nodeVisitor.accept(folder); - final int childDepth = depth + 1; - if (childDepth <= maxDepth) { - folder.folders().forEach(childFolder -> visit(childFolder, childDepth)); - folder.files().forEach(childFile -> visit(childFile, childDepth)); - } - afterFolderVisitor.accept(folder); - return this; - } - - private FolderVisitor visit(File file, int depth) { - nodeVisitor.accept(file); - fileVisitor.accept(file); - return this; - } - - public static class FolderVisitorBuilder { - - private Consumer beforeFolderVisitor = noOp(); - private Consumer afterFolderVisitor = noOp(); - private Consumer fileVisitor = noOp(); - private Consumer nodeVisitor = noOp(); - private int maxDepth = Integer.MAX_VALUE; - - private FolderVisitorBuilder() { - } - - public FolderVisitorBuilder beforeFolder(Consumer beforeFolderVisitor) { - if (beforeFolderVisitor == null) { - throw new IllegalArgumentException("Vistior may not be null"); - } - this.beforeFolderVisitor = beforeFolderVisitor; - return this; - } - - public FolderVisitorBuilder afterFolder(Consumer afterFolderVisitor) { - if (afterFolderVisitor == null) { - throw new IllegalArgumentException("Vistior may not be null"); - } - this.afterFolderVisitor = afterFolderVisitor; - return this; - } - - public FolderVisitorBuilder forEachFile(Consumer fileVisitor) { - if (fileVisitor == null) { - throw new IllegalArgumentException("Vistior may not be null"); - } - this.fileVisitor = fileVisitor; - return this; - } - - public FolderVisitorBuilder forEachNode(Consumer nodeVisitor) { - if (nodeVisitor == null) { - throw new IllegalArgumentException("Vistior may not be null"); - } - this.nodeVisitor = nodeVisitor; - return this; - } - - public FolderVisitorBuilder withMaxDepth(int maxDepth) { - if (maxDepth < 0) { - throw new IllegalArgumentException(format("maxDepth must not be smaller 0 but was %d", maxDepth)); - } - this.maxDepth = maxDepth; - return this; - } - - public FolderVisitor visit(Folder folder) { - return build().visit(folder); - } - - public FolderVisitor visit(File file) { - return build().visit(file); - } - - public FolderVisitor build() { - return new FolderVisitor(this); - } - - private static Consumer noOp() { - return ignoredParameter -> { - }; - } - - } - -} diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Node.java b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Node.java deleted file mode 100644 index 016c4906c..000000000 --- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Node.java +++ /dev/null @@ -1,100 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Markus Kreusch - * This file is licensed under the terms of the MIT license. - * See the LICENSE.txt file for more info. - ******************************************************************************/ -package org.cryptomator.filesystem; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.time.Instant; -import java.util.Optional; - -/** - * Represents a node, namely a {@link File} or {@link Folder}, in a - * {@link FileSystem}. - *

- * A node's identity (i.e. {@link #hashCode()} and {@link #equals(Object)}) depends on its parent node and its name (forming the node's path). - * These properties are meant to be immutable. This means that e.g. moving a node doesn't modify the node's identity but rather transfers properties to the destination node. - * - * @author Markus Kreusch - * @see Folder - * @see File - */ -public interface Node { - - String name() throws UncheckedIOException; - - /** - * @return Optional parent folder. No parent is present for the root node (see {@link FileSystem#parent()}). - */ - Optional parent() throws UncheckedIOException; - - /** - * @return true if the node exists. - */ - boolean exists() throws UncheckedIOException; - - /** - *

- * Deletes the node if it exists. - *

- * Does nothing if the node does not exist. - */ - void delete() throws UncheckedIOException; - - /** - *

- * Determines the last modified date of this node. - * - * @returns the last modified date of the file - */ - Instant lastModified() throws UncheckedIOException; - - /** - *

- * Sets the last modified date of the file. - * - * @param lastModified the time to set as creation time - */ - void setLastModified(Instant lastModified) throws UncheckedIOException; - - /** - *

- * Determines the creation time of this node. - *

- * Note: Getting the creation time may not be supported by all {@link FileSystem FileSystems}. - * - * @returns the creation time of the file or {@link Optional#empty()} if not supported - */ - default Optional creationTime() throws UncheckedIOException { - return Optional.empty(); - } - - /** - *

- * Sets the creation time of this node. - *

- * Setting the creation time may not be supported by all {@link FileSystem FileSystems}. If the {@code FileSystem} this {@code Node} belongs to does not support the - * setting the creation time the behavior of this method is unspecified. - * - * @param creationTime the time to set as creation time - */ - default void setCreationTime(Instant creationTime) throws UncheckedIOException { - throw new UncheckedIOException(new IOException("CreationTime not supported")); - } - - /** - * @return the {@link FileSystem} this Node belongs to - */ - default FileSystem fileSystem() { - return parent() // - .map(Node::fileSystem) // - .orElseGet(() -> (FileSystem) this); - } - - default boolean belongsToSameFilesystem(Node other) { - return fileSystem() == other.fileSystem(); - } - -} diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/OpenFiles.java b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/OpenFiles.java deleted file mode 100644 index 40a775067..000000000 --- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/OpenFiles.java +++ /dev/null @@ -1,71 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 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; - -import java.io.UncheckedIOException; -import java.util.Collection; -import java.util.Iterator; -import java.util.Map; -import java.util.stream.Stream; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class OpenFiles implements AutoCloseable { - - private final static Logger LOG = LoggerFactory.getLogger(OpenFiles.class); - - private final Map readableFiles; - private final Map writableFiles; - - public OpenFiles(Map readableFiles, Map writableFiles) { - this.readableFiles = readableFiles; - this.writableFiles = writableFiles; - } - - @Override - public void close() throws UncheckedIOException { - OpenFiles.cleanup(readableFiles.values(), writableFiles.values()); - } - - public ReadableFile readable(File file) { - return readableFiles.computeIfAbsent(file, fileNotOpenForReading -> { - throw new IllegalArgumentException(String.format("File %s is not open for reading", fileNotOpenForReading)); - }); - } - - public WritableFile writable(File file) { - return writableFiles.computeIfAbsent(file, fileNotOpenForWriting -> { - throw new IllegalArgumentException(String.format("File %s is not open for writing", fileNotOpenForWriting)); - }); - } - - static void cleanup(Collection readableFiles, Collection writableFiles) { - Iterator iterator = Stream.concat(readableFiles.stream(), writableFiles.stream()).iterator(); - UncheckedIOException firstException = null; - while (iterator.hasNext()) { - AutoCloseable openFile = iterator.next(); - try { - openFile.close(); - } catch (UncheckedIOException e) { - if (firstException == null) { - firstException = e; - } else { - firstException.addSuppressed(e); - } - } catch (Exception e) { - LOG.error("Unexpected exception during close on " + openFile.getClass().getSimpleName(), e); - } - } - if (firstException != null) { - throw firstException; - } - } - -} diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/PathResolver.java b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/PathResolver.java deleted file mode 100644 index 4f8ad8e77..000000000 --- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/PathResolver.java +++ /dev/null @@ -1,112 +0,0 @@ -package org.cryptomator.filesystem; - -import java.io.FileNotFoundException; -import java.io.UncheckedIOException; -import java.util.Arrays; -import java.util.Iterator; - -import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.StringUtils; - -final class PathResolver { - - private static final String DOT = "."; - private static final String DOTDOT = ".."; - - private PathResolver() { - } - - /** - * Resolves a relative path (separated by '/') to a folder, e.g. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
dirpathresult
/foo/barfoo/bar/foo/bar/foo/bar
/foo/bar../baz/foo/baz
/foo/bar./foo/../foo/bar
/foo/bar//foo/bar
/foo/bar/foo/bar
/foo/bar../../..Exception
- * - * @param dir The directory from which to resolve the path. - * @param relativePath The path relative to a given directory. - * @return The folder with the given path relative to the given dir. - */ - public static Folder resolveFolder(Folder dir, String relativePath) { - final String[] fragments = StringUtils.split(relativePath, '/'); - if (ArrayUtils.isEmpty(fragments)) { - return dir; - } - return resolveFolder(dir, Arrays.stream(fragments).iterator()); - } - - /** - * Resolves a relative path (separated by '/') to a file. Besides returning a File, this method is identical to {@link #resolveFile(Folder, String)}. - * - * @param dir The directory from which to resolve the path. - * @param relativePath The path relative to a given directory. - * @return The file with the given path relative to the given dir. - * @throws IllegalArgumentException - * if relativePath is empty, as this path would resolve to the directory itself, which obviously can't be a file. - */ - public static File resolveFile(Folder dir, String relativePath) { - final String[] fragments = StringUtils.split(relativePath, '/'); - if (ArrayUtils.isEmpty(fragments)) { - throw new IllegalArgumentException("Empty relativePath"); - } - final Folder folder = resolveFolder(dir, Arrays.stream(fragments).limit(fragments.length - 1).iterator()); - final String filename = fragments[fragments.length - 1]; - return folder.file(filename); - } - - private static Folder resolveFolder(Folder dir, Iterator remainingPathFragments) { - if (!remainingPathFragments.hasNext()) { - return dir; - } - final String fragment = remainingPathFragments.next(); - assert fragment.length() > 0 : "iterator must not contain empty fragments"; - if (DOT.equals(fragment)) { - return resolveFolder(dir, remainingPathFragments); - } else if (DOTDOT.equals(fragment) && dir.parent().isPresent()) { - return resolveFolder(dir.parent().get(), remainingPathFragments); - } else if (DOTDOT.equals(fragment) && !dir.parent().isPresent()) { - throw new UncheckedIOException(new FileNotFoundException("Unresolvable path")); - } else { - return resolveFolder(dir.folder(fragment), remainingPathFragments); - } - } - -} diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/ReadableFile.java b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/ReadableFile.java deleted file mode 100644 index 300ad32ec..000000000 --- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/ReadableFile.java +++ /dev/null @@ -1,50 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Markus Kreusch - * This file is licensed under the terms of the MIT license. - * See the LICENSE.txt file for more info. - ******************************************************************************/ -package org.cryptomator.filesystem; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.nio.channels.ReadableByteChannel; - -public interface ReadableFile extends ReadableByteChannel { - - /** - *

- * Tries to fill the remaining space in the given byte buffer with data from - * this readable bytes from the current position. - *

- * May read less bytes if the end of this readable bytes has been reached. - * - * @param target - * the byte buffer to fill - * @return the number of bytes actually read, or {@code -1} if the end of - * file has been reached - * @throws UncheckedIOException - * if an {@link IOException} occurs while reading from this - * {@code ReadableBytes} - */ - @Override - int read(ByteBuffer target) throws UncheckedIOException; - - /** - *

- * Fast-forwards or rewinds the file to the specified position. - *

- * Consecutive reads on the file will begin at the new position. - * - * @param position - * the position to set the file to - * @throws UncheckedIOException - * if an {@link IOException} occurs - * - */ - void position(long position) throws UncheckedIOException; - - @Override - void close() throws UncheckedIOException; - -} diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/WritableFile.java b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/WritableFile.java deleted file mode 100644 index d5080ae79..000000000 --- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/WritableFile.java +++ /dev/null @@ -1,63 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Markus Kreusch - * This file is licensed under the terms of the MIT license. - * See the LICENSE.txt file for more info. - ******************************************************************************/ -package org.cryptomator.filesystem; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.nio.channels.WritableByteChannel; - -public interface WritableFile extends WritableByteChannel { - - void truncate() throws UncheckedIOException; - - /** - * Writes the data in the given byte buffer to this readable bytes at the - * current position. - * - * @param source - * the byte buffer to use - * @return the number of bytes written, always equal to - * {@code source.remaining()} - * @throws UncheckedIOException - * if an {@link IOException} occurs while writing - */ - @Override - int write(ByteBuffer source) throws UncheckedIOException; - - /** - *

- * Fast-forwards or rewinds the file to the specified position. - *

- * Consecutive writes on the file will begin at the new position. - *

- * If the position is set to a value greater than the current end of file - * consecutive writes will write data to the given position. The value of - * all bytes between this position and the previous end of file will be - * unspecified. - * - * @param position - * the position to set the file to - * @throws UncheckedIOException - * if an {@link IOException} occurs - */ - void position(long position) throws UncheckedIOException; - - /** - *

- * Closes this {@code WritableFile} which finally commits all operations - * performed on it to the underlying file system. - *

- * After a {@code WritableFile} has been closed all other operations will - * throw an {@link UncheckedIOException}. - *

- * Invoking this method on a {@link WritableFile} which has already been - * closed does nothing. - */ - @Override - void close() throws UncheckedIOException; - -} diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingFile.java b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingFile.java deleted file mode 100644 index af119c90c..000000000 --- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingFile.java +++ /dev/null @@ -1,82 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 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.delegating; - -import java.io.UncheckedIOException; -import java.util.Optional; - -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.filesystem.WritableFile; - -public abstract class DelegatingFile> extends DelegatingNode implements File { - - private final D parent; - - public DelegatingFile(D parent, File delegate) { - super(delegate); - this.parent = parent; - } - - @Override - public Optional parent() throws UncheckedIOException { - return Optional.of(parent); - } - - @Override - public long size() throws UncheckedIOException { - return delegate.size(); - } - - @Override - public ReadableFile openReadable() throws UncheckedIOException { - return delegate.openReadable(); - } - - @Override - public WritableFile openWritable() throws UncheckedIOException { - return delegate.openWritable(); - } - - @Override - public void copyTo(File destination) { - if (getClass().equals(destination.getClass())) { - final File delegateDest = ((DelegatingFile) destination).delegate; - delegate.copyTo(delegateDest); - } else { - delegate.copyTo(destination); - } - } - - @Override - public void moveTo(File destination) { - if (getClass().equals(destination.getClass())) { - final File delegateDest = ((DelegatingFile) destination).delegate; - delegate.moveTo(delegateDest); - } else { - throw new IllegalArgumentException("Can only move DelegatingFile to other DelegatingFile."); - } - } - - @Override - public void delete() throws UncheckedIOException { - delegate.delete(); - } - - @Override - public int compareTo(File o) { - if (getClass().equals(o.getClass())) { - final File delegateOther = ((DelegatingFile) o).delegate; - return delegate.compareTo(delegateOther); - } else { - return delegate.compareTo(o); - } - } - -} diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingFileSystem.java b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingFileSystem.java deleted file mode 100644 index ee3702cef..000000000 --- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingFileSystem.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.cryptomator.filesystem.delegating; - -import java.util.Optional; - -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.Folder; - -public interface DelegatingFileSystem extends FileSystem { - - Folder getDelegate(); - - @Override - default Optional quotaUsedBytes() { - return getDelegate().fileSystem().quotaUsedBytes(); - } - - @Override - default Optional quotaAvailableBytes() { - return getDelegate().fileSystem().quotaAvailableBytes(); - } - -} diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingFolder.java b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingFolder.java deleted file mode 100644 index 68a963f6d..000000000 --- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingFolder.java +++ /dev/null @@ -1,100 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 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.delegating; - -import java.io.UncheckedIOException; -import java.util.Optional; -import java.util.stream.Stream; - -import org.cryptomator.common.WeakValuedCache; -import org.cryptomator.common.streams.AutoClosingStream; -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.Node; - -public abstract class DelegatingFolder, F extends DelegatingFile> extends DelegatingNodeimplements Folder { - - private final D parent; - private final WeakValuedCache folders = WeakValuedCache.usingLoader(this::newFolder); - private final WeakValuedCache files = WeakValuedCache.usingLoader(this::newFile); - - public DelegatingFolder(D parent, Folder delegate) { - super(delegate); - this.parent = parent; - } - - @Override - public Optional parent() throws UncheckedIOException { - return Optional.ofNullable(parent); - } - - @Override - public Stream children() throws UncheckedIOException { - return AutoClosingStream.from(Stream.concat(folders(), files())); - } - - @Override - public Stream folders() { - return delegate.folders().map(folders::get); - } - - @Override - public Stream files() throws UncheckedIOException { - return delegate.files().map(files::get); - } - - @Override - public F file(String name) throws UncheckedIOException { - return files.get(delegate.file(name)); - } - - protected abstract F newFile(File delegate); - - @Override - public D folder(String name) throws UncheckedIOException { - return folders.get(delegate.folder(name)); - } - - protected abstract D newFolder(Folder delegate); - - @Override - public void create() throws UncheckedIOException { - if (exists()) { - return; - } - parent().ifPresent(p -> p.create()); - delegate.create(); - } - - @Override - public void delete() { - delegate.delete(); - } - - @Override - public void copyTo(Folder destination) throws UncheckedIOException { - if (destination instanceof DelegatingFolder) { - final Folder delegateDest = ((DelegatingFolder) destination).delegate; - delegate.copyTo(delegateDest); - } else { - throw new IllegalArgumentException("Can only copy DelegatingFolder to other DelegatingFolder."); - } - } - - @Override - public void moveTo(Folder destination) { - if (getClass().equals(destination.getClass())) { - final Folder delegateDest = ((DelegatingFolder) destination).delegate; - delegate.moveTo(delegateDest); - } else { - throw new IllegalArgumentException("Can only move DelegatingFolder to other DelegatingFolder."); - } - } - -} diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingNode.java b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingNode.java deleted file mode 100644 index c5e11cb77..000000000 --- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingNode.java +++ /dev/null @@ -1,78 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 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.delegating; - -import java.io.UncheckedIOException; -import java.time.Instant; -import java.util.Optional; - -import org.cryptomator.filesystem.Node; - -public abstract class DelegatingNode implements Node { - - protected final T delegate; - - public DelegatingNode(T delegate) { - if (delegate == null) { - throw new IllegalArgumentException("Delegate must not be null"); - } - this.delegate = delegate; - } - - @Override - public String name() throws UncheckedIOException { - return delegate.name(); - } - - @Override - public boolean exists() throws UncheckedIOException { - return delegate.exists(); - } - - @Override - public Instant lastModified() throws UncheckedIOException { - return delegate.lastModified(); - } - - @Override - public void setLastModified(Instant instant) throws UncheckedIOException { - delegate.setLastModified(instant); - } - - @Override - public Optional creationTime() throws UncheckedIOException { - return delegate.creationTime(); - } - - @Override - public void setCreationTime(Instant instant) throws UncheckedIOException { - delegate.setCreationTime(instant); - } - - @Override - public int hashCode() { - return delegate.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof DelegatingNode) { - DelegatingNode other = (DelegatingNode) obj; - return this.delegate.equals(other.delegate); - } else { - return false; - } - } - - @Override - public String toString() { - return "Delegate[" + delegate + "]"; - } - -} diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingReadableFile.java b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingReadableFile.java deleted file mode 100644 index d9a022d06..000000000 --- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingReadableFile.java +++ /dev/null @@ -1,44 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 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.delegating; - -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; - -import org.cryptomator.filesystem.ReadableFile; - -public class DelegatingReadableFile implements ReadableFile { - - private final ReadableFile delegate; - - public DelegatingReadableFile(ReadableFile delegate) { - this.delegate = delegate; - } - - @Override - public boolean isOpen() { - return delegate.isOpen(); - } - - @Override - public int read(ByteBuffer target) throws UncheckedIOException { - return delegate.read(target); - } - - @Override - public void position(long position) throws UncheckedIOException { - delegate.position(position); - } - - @Override - public void close() throws UncheckedIOException { - delegate.close(); - } - -} diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingWritableFile.java b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingWritableFile.java deleted file mode 100644 index 8abe702a5..000000000 --- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingWritableFile.java +++ /dev/null @@ -1,49 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 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.delegating; - -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; - -import org.cryptomator.filesystem.WritableFile; - -public class DelegatingWritableFile implements WritableFile { - - final WritableFile delegate; - - public DelegatingWritableFile(WritableFile delegate) { - this.delegate = delegate; - } - - @Override - public boolean isOpen() { - return delegate.isOpen(); - } - - @Override - public void truncate() throws UncheckedIOException { - delegate.truncate(); - } - - @Override - public int write(ByteBuffer source) throws UncheckedIOException { - return delegate.write(source); - } - - @Override - public void position(long position) throws UncheckedIOException { - delegate.position(position); - } - - @Override - public void close() throws UncheckedIOException { - delegate.close(); - } - -} diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/package-info.java b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/package-info.java deleted file mode 100644 index 481a08372..000000000 --- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/package-info.java +++ /dev/null @@ -1,13 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 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 - *******************************************************************************/ -/** - * Defines a file system abstraction to allow access to real and virtual file - * systems through a common API. - */ -package org.cryptomator.filesystem; \ No newline at end of file diff --git a/main/filesystem-api/src/main/java/org/cryptomator/io/ByteBuffers.java b/main/filesystem-api/src/main/java/org/cryptomator/io/ByteBuffers.java deleted file mode 100644 index 7b6de50d1..000000000 --- a/main/filesystem-api/src/main/java/org/cryptomator/io/ByteBuffers.java +++ /dev/null @@ -1,35 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 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.io; - -import java.nio.ByteBuffer; - -public final class ByteBuffers { - - private ByteBuffers() { - } - - /** - * Copies as many bytes as possible from the given source to the destination buffer. - * The position of both buffers will be incremented by as many bytes as have been copied. - * - * @param source ByteBuffer from which bytes are read - * @param destination ByteBuffer into which bytes are written - * @return number of bytes copied, i.e. {@link ByteBuffer#remaining() source.remaining()} or {@link ByteBuffer#remaining() destination.remaining()}, whatever is less. - */ - public static int copy(ByteBuffer source, ByteBuffer destination) { - final int numBytes = Math.min(source.remaining(), destination.remaining()); - final ByteBuffer tmp = source.asReadOnlyBuffer(); - tmp.limit(tmp.position() + numBytes); - destination.put(tmp); - source.position(tmp.position()); // until now only tmp pos has been incremented, so we need to adjust the position - return numBytes; - } - -} diff --git a/main/filesystem-api/src/main/java/org/cryptomator/io/FileContents.java b/main/filesystem-api/src/main/java/org/cryptomator/io/FileContents.java deleted file mode 100644 index 32e5adac7..000000000 --- a/main/filesystem-api/src/main/java/org/cryptomator/io/FileContents.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.cryptomator.io; - -import java.io.IOException; -import java.io.Reader; -import java.io.UncheckedIOException; -import java.nio.channels.Channels; -import java.nio.channels.ReadableByteChannel; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; - -import org.apache.commons.io.IOUtils; -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.WritableFile; - -public final class FileContents { - - public static final FileContents UTF_8 = FileContents.withCharset(StandardCharsets.UTF_8); - - private final Charset charset; - - private FileContents(Charset charset) { - this.charset = charset; - } - - /** - * Reads the whole content from the given file. - * - * @param file File whose content should be read. - * @return The file's content interpreted in this FileContents' charset. - */ - public String readContents(File file) { - try ( // - ReadableByteChannel channel = file.openReadable(); // - Reader reader = Channels.newReader(channel, charset.newDecoder(), -1)) { - return IOUtils.toString(reader); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - /** - * Writes the string into the file encoded with this FileContents' charset. - * This methods replaces any previously existing content, i.e. the string will be the sole content. - * - * @param file File whose content should be written. - * @param content The new content. - */ - public void writeContents(File file, String content) { - try (WritableFile writable = file.openWritable()) { - writable.truncate(); - writable.write(charset.encode(content)); - } - } - - public static FileContents withCharset(Charset charset) { - return new FileContents(charset); - } - -} diff --git a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/ByteBufferMatcher.java b/main/filesystem-api/src/test/java/org/cryptomator/filesystem/ByteBufferMatcher.java deleted file mode 100644 index 23e4c50a6..000000000 --- a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/ByteBufferMatcher.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.cryptomator.filesystem; - -import java.nio.ByteBuffer; - -import org.hamcrest.Description; -import org.hamcrest.Matcher; -import org.hamcrest.TypeSafeDiagnosingMatcher; - -public class ByteBufferMatcher { - - public static Matcher byteBufferFilledWith(int value) { - if (((byte) value) != value) { - throw new IllegalArgumentException("Invalid byte value"); - } - return new TypeSafeDiagnosingMatcher(ByteBuffer.class) { - - @Override - public void describeTo(Description description) { - description.appendText("a byte buffer filled with " + value); - } - - @Override - protected boolean matchesSafely(ByteBuffer item, Description mismatchDescription) { - while (item.hasRemaining()) { - byte currentValue = item.get(); - if (currentValue != value) { - mismatchDescription.appendText("a byte buffer containing also " + currentValue); - return false; - } - } - return true; - } - }; - } - -} diff --git a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/CopierTest.java b/main/filesystem-api/src/test/java/org/cryptomator/filesystem/CopierTest.java deleted file mode 100644 index a00252e85..000000000 --- a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/CopierTest.java +++ /dev/null @@ -1,227 +0,0 @@ -package org.cryptomator.filesystem; - -import static java.util.Arrays.asList; -import static org.cryptomator.common.test.matcher.ContainsMatcher.contains; -import static org.cryptomator.common.test.mockito.Answers.collectParameters; -import static org.cryptomator.common.test.mockito.Answers.consecutiveAnswers; -import static org.cryptomator.common.test.mockito.Answers.value; -import static org.cryptomator.filesystem.File.EOF; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Collection; -import java.util.stream.Stream; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; -import org.mockito.InOrder; -import org.mockito.Mock; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; -import org.mockito.stubbing.Answer; - -import de.bechte.junit.runners.context.HierarchicalContextRunner; - -@RunWith(HierarchicalContextRunner.class) -public class CopierTest { - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @Rule - public MockitoRule mockitoRule = MockitoJUnit.rule(); - - public class CopyFiles { - - @Mock - private File source; - - @Mock - private File destination; - - @Mock - private ReadableFile readable; - - @Mock - private WritableFile writable; - - @Before - public void setUp() { - when(source.openReadable()).thenReturn(readable); - when(destination.openWritable()).thenReturn(writable); - } - - @Test - public void testCopyFileOpensFilesInSortedOrderIfSourceIsSmallerDestination() { - mockCompareToWithOrder(source, destination); - when(readable.read(any())).thenReturn(EOF); - - Copier.copy(source, destination); - - InOrder inOrder = inOrder(source, destination); - inOrder.verify(source).openReadable(); - inOrder.verify(destination).openWritable(); - } - - @Test - public void testCopyFileOpensFilesInSortedOrderIfDestinationIsSmallerSource() { - mockCompareToWithOrder(destination, source); - when(readable.read(any())).thenReturn(EOF); - - Copier.copy(source, destination); - - InOrder inOrder = inOrder(source, destination); - inOrder.verify(destination).openWritable(); - inOrder.verify(source).openReadable(); - } - - @Test - public void testCopyFileReadsAndWritesReadableSourceAndWritableDestintationUntilEof() { - int irrelevantValue = 0; - Collection written = new ArrayList<>(); - mockCompareToWithOrder(source, destination); - byte[] read1 = {1, 48, 32, 33, 22}; - byte[] read2 = {4, 3, 1, -2, -8}; - when(readable.read(any())).then(consecutiveAnswers(fillBufferWith(read1), fillBufferWith(read2), value(EOF))); - when(writable.write(any())).then(collectParameters(value(irrelevantValue), (ByteBuffer buffer) -> { - byte[] data = new byte[buffer.remaining()]; - buffer.get(data); - written.add(data); - })); - - Copier.copy(source, destination); - - InOrder inOrder = inOrder(readable, writable); - inOrder.verify(writable).truncate(); - inOrder.verify(readable).read(any()); - inOrder.verify(writable).write(any()); - inOrder.verify(readable).read(any()); - inOrder.verify(writable).write(any()); - inOrder.verify(readable).read(any()); - inOrder.verify(readable).close(); - inOrder.verify(writable).close(); - - assertThat(written, contains(is(read1), is(read2))); - } - - private Answer fillBufferWith(byte[] data) { - return new Answer() { - @Override - public Integer answer(InvocationOnMock invocation) throws Throwable { - ByteBuffer buffer = invocation.getArgumentAt(0, ByteBuffer.class); - for (byte value : data) { - buffer.put(value); - } - return data.length; - } - - }; - } - - private void mockCompareToWithOrder(File first, File last) { - when(first.compareTo(last)).thenReturn(-1); - when(last.compareTo(first)).thenReturn(1); - } - - } - - public class CopyFolders { - - @Mock - private Folder source; - - @Mock - private Folder destination; - - @Test - public void testCopyFolderDeletesAndCreatesDestinationBeforeIteratingOverTheFilesAndFoldersInSource() { - when(source.files()).thenReturn(Stream.empty()); - when(source.folders()).thenReturn(Stream.empty()); - - Copier.copy(source, destination); - - InOrder inOrder = inOrder(source, destination); - inOrder.verify(destination).delete(); - inOrder.verify(destination).create(); - inOrder.verify(source).files(); - inOrder.verify(source).folders(); - } - - @Test - @SuppressWarnings({"unchecked", "rawtypes"}) - public void testCopyFolderInvokesCopyToOnAllFilesInSourceWithFileWithSameNameFromDestination() { - String filename1 = "nameOfFile1"; - String filename2 = "nameOfFile2"; - File file1 = mock(File.class); - File file2 = mock(File.class); - File destinationFile1 = mock(File.class); - File destinationFile2 = mock(File.class); - when(source.files()).thenReturn((Stream) asList(file1, file2).stream()); - when(source.folders()).thenReturn(Stream.empty()); - when(destination.file(filename1)).thenReturn(destinationFile1); - when(destination.file(filename2)).thenReturn(destinationFile2); - when(file1.name()).thenReturn(filename1); - when(file2.name()).thenReturn(filename2); - - Copier.copy(source, destination); - - verify(file1).copyTo(destinationFile1); - verify(file2).copyTo(destinationFile2); - } - - @Test - @SuppressWarnings({"unchecked", "rawtypes"}) - public void testCopyFolderInvokesCopyToOnAllFoldersInSourceWithFolderWithSameNameFromDestination() { - String folderName1 = "nameOfFolder1"; - String folderName2 = "nameOfFolder2"; - Folder folder1 = mock(Folder.class); - Folder folder2 = mock(Folder.class); - Folder destinationfolder1 = mock(Folder.class); - Folder destinationfolder2 = mock(Folder.class); - when(source.folders()).thenReturn((Stream) asList(folder1, folder2).stream()); - when(source.files()).thenReturn(Stream.empty()); - when(destination.folder(folderName1)).thenReturn(destinationfolder1); - when(destination.folder(folderName2)).thenReturn(destinationfolder2); - when(folder1.name()).thenReturn(folderName1); - when(folder2.name()).thenReturn(folderName2); - - Copier.copy(source, destination); - - verify(folder1).copyTo(destinationfolder1); - verify(folder2).copyTo(destinationfolder2); - } - - @Test - public void testCopyFolderFailsWithIllegalArgumentExceptionIfSourceIsNestedInDestination() { - when(source.isAncestorOf(destination)).thenReturn(false); - when(destination.isAncestorOf(source)).thenReturn(true); - - thrown.expect(IllegalArgumentException.class); - - Copier.copy(source, destination); - } - - @Test - public void testCopyFolderFailsWithIllegalArgumentExceptionIfDestinationIsNestedInSource() { - when(source.isAncestorOf(destination)).thenReturn(true); - when(destination.isAncestorOf(source)).thenReturn(false); - - thrown.expect(IllegalArgumentException.class); - - Copier.copy(source, destination); - } - - } - -} diff --git a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/PathResolverTest.java b/main/filesystem-api/src/test/java/org/cryptomator/filesystem/PathResolverTest.java deleted file mode 100644 index af1186a8c..000000000 --- a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/PathResolverTest.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.cryptomator.filesystem; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.util.Optional; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; - -public class PathResolverTest { - - private final Folder root = Mockito.mock(Folder.class); - private final Folder foo = Mockito.mock(Folder.class); - private final Folder bar = Mockito.mock(Folder.class); - private final File baz = Mockito.mock(File.class); - - @Before - public void configureMocks() throws IOException { - Mockito.doReturn(Optional.empty()).when(root).parent(); - Mockito.doReturn(Optional.of(root)).when(foo).parent(); - Mockito.doReturn(Optional.of(foo)).when(bar).parent(); - - Mockito.doReturn(foo).when(root).folder("foo"); - Mockito.doReturn(bar).when(foo).folder("bar"); - Mockito.doReturn(baz).when(bar).file("baz"); - } - - @Test - public void testResolveSameFolder() { - Assert.assertEquals(foo, PathResolver.resolveFolder(foo, "")); - Assert.assertEquals(foo, PathResolver.resolveFolder(foo, "/")); - Assert.assertEquals(foo, PathResolver.resolveFolder(foo, "///")); - } - - @Test - public void testResolveChildFolder() { - Assert.assertEquals(bar, PathResolver.resolveFolder(root, "foo/bar")); - Assert.assertEquals(bar, PathResolver.resolveFolder(root, "foo/./bar")); - Assert.assertEquals(bar, PathResolver.resolveFolder(root, "./foo/././bar")); - } - - @Test - public void testResolveParentFolder() { - Assert.assertEquals(foo, PathResolver.resolveFolder(bar, "..")); - Assert.assertEquals(root, PathResolver.resolveFolder(bar, "../..")); - } - - @Test - public void testResolveSiblingFolder() { - Assert.assertEquals(foo, PathResolver.resolveFolder(bar, "../../foo")); - } - - @Test(expected = UncheckedIOException.class) - public void testResolveUnresolvableFolder() { - PathResolver.resolveFolder(root, ".."); - } - - @Test(expected = IllegalArgumentException.class) - public void testResolveFileWithEmptyPath() { - PathResolver.resolveFile(root, ""); - } - - @Test - public void testResolveFile() { - Assert.assertEquals(baz, PathResolver.resolveFile(foo, "../foo/bar/./baz")); - } - -} diff --git a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingFileSystemTest.java b/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingFileSystemTest.java deleted file mode 100644 index 8c4ff82c6..000000000 --- a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingFileSystemTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.cryptomator.filesystem.delegating; - -import java.util.Optional; - -import org.cryptomator.filesystem.FileSystem; -import org.junit.Assert; -import org.junit.Test; -import org.mockito.Mockito; - -public class DelegatingFileSystemTest { - - @Test - public void testQuotaAvailableBytes() { - FileSystem mockFs = Mockito.mock(FileSystem.class); - Mockito.when(mockFs.fileSystem()).thenReturn(mockFs); - Mockito.when(mockFs.quotaAvailableBytes()).thenReturn(Optional.of(42l)); - - DelegatingFileSystem delegatingFs = TestDelegatingFileSystem.withRoot(mockFs); - Assert.assertEquals(mockFs.quotaAvailableBytes(), delegatingFs.quotaAvailableBytes()); - } - - @Test - public void testQuotaUsedBytes() { - FileSystem mockFs = Mockito.mock(FileSystem.class); - Mockito.when(mockFs.fileSystem()).thenReturn(mockFs); - Mockito.when(mockFs.quotaUsedBytes()).thenReturn(Optional.of(23l)); - - DelegatingFileSystem delegatingFs = TestDelegatingFileSystem.withRoot(mockFs); - Assert.assertEquals(mockFs.quotaUsedBytes(), delegatingFs.quotaUsedBytes()); - } - -} diff --git a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingFileTest.java b/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingFileTest.java deleted file mode 100644 index 36365d3e2..000000000 --- a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingFileTest.java +++ /dev/null @@ -1,186 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 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.delegating; - -import java.time.Instant; -import java.util.Optional; - -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.filesystem.WritableFile; -import org.junit.Assert; -import org.junit.Test; -import org.mockito.Mockito; - -public class DelegatingFileTest { - - @Test - public void testName() { - File mockFile = Mockito.mock(File.class); - DelegatingFile delegatingFile = new TestDelegatingFile(null, mockFile); - - Mockito.when(mockFile.name()).thenReturn("Test"); - Assert.assertEquals(mockFile.name(), delegatingFile.name()); - } - - @Test - public void testSize() { - File mockFile = Mockito.mock(File.class); - DelegatingFile delegatingFile = new TestDelegatingFile(null, mockFile); - - Mockito.when(mockFile.size()).thenReturn(42l); - Assert.assertEquals(42l, delegatingFile.size()); - Mockito.verify(mockFile).size(); - } - - @Test - public void testParent() { - Folder mockFolder = Mockito.mock(Folder.class); - File mockFile = Mockito.mock(File.class); - - TestDelegatingFileSystem delegatingParent = TestDelegatingFileSystem.withRoot(mockFolder); - DelegatingFile delegatingFile = new TestDelegatingFile(delegatingParent, mockFile); - Assert.assertEquals(delegatingParent, delegatingFile.parent().get()); - } - - @Test - public void testExists() { - File mockFile = Mockito.mock(File.class); - DelegatingFile delegatingFile = new TestDelegatingFile(null, mockFile); - - Mockito.when(mockFile.exists()).thenReturn(true); - Assert.assertTrue(delegatingFile.exists()); - - Mockito.when(mockFile.exists()).thenReturn(false); - Assert.assertFalse(delegatingFile.exists()); - } - - @Test - public void testLastModified() { - File mockFile = Mockito.mock(File.class); - DelegatingFile delegatingFile = new TestDelegatingFile(null, mockFile); - - Instant now = Instant.now(); - Mockito.when(mockFile.lastModified()).thenReturn(now); - Assert.assertEquals(now, delegatingFile.lastModified()); - } - - @Test - public void testSetLastModified() { - File mockFile = Mockito.mock(File.class); - DelegatingFile delegatingFile = new TestDelegatingFile(null, mockFile); - - Instant now = Instant.now(); - delegatingFile.setLastModified(now); - Mockito.verify(mockFile).setLastModified(now); - } - - @Test - public void testCreationTime() { - File mockFile = Mockito.mock(File.class); - DelegatingFile delegatingFile = new TestDelegatingFile(null, mockFile); - - Instant now = Instant.now(); - Mockito.when(mockFile.creationTime()).thenReturn(Optional.of(now)); - Assert.assertEquals(now, delegatingFile.creationTime().get()); - } - - @Test - public void testSetCreationTime() { - File mockFile = Mockito.mock(File.class); - DelegatingFile delegatingFile = new TestDelegatingFile(null, mockFile); - - Instant now = Instant.now(); - delegatingFile.setCreationTime(now); - Mockito.verify(mockFile).setCreationTime(now); - } - - @Test - public void testOpenReadable() { - File mockFile = Mockito.mock(File.class); - ReadableFile mockReadableFile = Mockito.mock(ReadableFile.class); - - Mockito.when(mockFile.openReadable()).thenReturn(mockReadableFile); - DelegatingFile delegatingFile = new TestDelegatingFile(null, mockFile); - Assert.assertNotNull(delegatingFile.openReadable()); - } - - @Test - public void testOpenWritable() { - File mockFile = Mockito.mock(File.class); - WritableFile mockWritableFile = Mockito.mock(WritableFile.class); - - Mockito.when(mockFile.openWritable()).thenReturn(mockWritableFile); - DelegatingFile delegatingFile = new TestDelegatingFile(null, mockFile); - Assert.assertNotNull(delegatingFile.openWritable()); - } - - @Test - public void testMoveTo() { - File mockFile1 = Mockito.mock(File.class); - File mockFile2 = Mockito.mock(File.class); - DelegatingFile delegatingFile1 = new TestDelegatingFile(null, mockFile1); - DelegatingFile delegatingFile2 = new TestDelegatingFile(null, mockFile2); - - delegatingFile1.moveTo(delegatingFile2); - Mockito.verify(mockFile1).moveTo(mockFile2); - } - - @Test(expected = IllegalArgumentException.class) - public void testMoveToDestinationFromDifferentLayer() { - File mockFile1 = Mockito.mock(File.class); - File mockFile2 = Mockito.mock(File.class); - DelegatingFile delegatingFile1 = new TestDelegatingFile(null, mockFile1); - - delegatingFile1.moveTo(mockFile2); - } - - @Test - public void testCopyTo() { - File mockFile1 = Mockito.mock(File.class); - File mockFile2 = Mockito.mock(File.class); - DelegatingFile delegatingFile1 = new TestDelegatingFile(null, mockFile1); - DelegatingFile delegatingFile2 = new TestDelegatingFile(null, mockFile2); - - delegatingFile1.copyTo(delegatingFile2); - Mockito.verify(mockFile1).copyTo(mockFile2); - } - - @Test - public void testCopyToDestinationFromDifferentLayer() { - File mockFile1 = Mockito.mock(File.class); - File mockFile2 = Mockito.mock(File.class); - DelegatingFile delegatingFile1 = new TestDelegatingFile(null, mockFile1); - - delegatingFile1.copyTo(mockFile2); - Mockito.verify(mockFile1).copyTo(mockFile2); - } - - @Test - public void testDelete() { - File mockFile = Mockito.mock(File.class); - DelegatingFile delegatingFile = new TestDelegatingFile(null, mockFile); - - delegatingFile.delete(); - Mockito.verify(mockFile).delete(); - } - - @Test - public void testCompareTo() { - File mockFile1 = Mockito.mock(File.class); - File mockFile2 = Mockito.mock(File.class); - - Mockito.when(mockFile1.compareTo(mockFile2)).thenReturn(-1); - DelegatingFile delegatingFile1 = new TestDelegatingFile(null, mockFile1); - DelegatingFile delegatingFile2 = new TestDelegatingFile(null, mockFile2); - Assert.assertEquals(-1, delegatingFile1.compareTo(delegatingFile2)); - } - -} diff --git a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingFolderTest.java b/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingFolderTest.java deleted file mode 100644 index f7cc443d7..000000000 --- a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingFolderTest.java +++ /dev/null @@ -1,206 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 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.delegating; - -import java.time.Instant; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.Node; -import org.hamcrest.Matchers; -import org.junit.Assert; -import org.junit.Test; -import org.mockito.Mockito; - -public class DelegatingFolderTest { - - @Test - public void testName() { - Folder mockFolder = Mockito.mock(Folder.class); - DelegatingFolder delegatingFolder = new TestDelegatingFolder(null, mockFolder); - - Mockito.when(mockFolder.name()).thenReturn("Test"); - Assert.assertEquals(mockFolder.name(), delegatingFolder.name()); - } - - @Test - public void testParent() { - Folder mockFolder1 = Mockito.mock(Folder.class); - Folder mockFolder2 = Mockito.mock(Folder.class); - - TestDelegatingFileSystem delegatingParent = TestDelegatingFileSystem.withRoot(mockFolder1); - DelegatingFolder delegatingFolder = new TestDelegatingFolder(delegatingParent, mockFolder2); - Assert.assertEquals(delegatingParent, delegatingFolder.parent().get()); - } - - @Test - public void testExists() { - Folder mockFolder = Mockito.mock(Folder.class); - DelegatingFolder delegatingFolder = new TestDelegatingFolder(null, mockFolder); - - Mockito.when(mockFolder.exists()).thenReturn(true); - Assert.assertTrue(delegatingFolder.exists()); - - Mockito.when(mockFolder.exists()).thenReturn(false); - Assert.assertFalse(delegatingFolder.exists()); - } - - @Test - public void testLastModified() { - Folder mockFolder = Mockito.mock(Folder.class); - DelegatingFolder delegatingFolder = new TestDelegatingFolder(null, mockFolder); - - Instant now = Instant.now(); - Mockito.when(mockFolder.lastModified()).thenReturn(now); - Assert.assertEquals(now, delegatingFolder.lastModified()); - } - - @Test - public void testSetLastModified() { - Folder mockFolder = Mockito.mock(Folder.class); - DelegatingFolder delegatingFolder = new TestDelegatingFolder(null, mockFolder); - - Instant now = Instant.now(); - delegatingFolder.setLastModified(now); - Mockito.verify(mockFolder).setLastModified(now); - } - - @Test - public void testCreationTime() { - Folder mockFolder = Mockito.mock(Folder.class); - DelegatingFolder delegatingFolder = new TestDelegatingFolder(null, mockFolder); - - Instant now = Instant.now(); - Mockito.when(mockFolder.creationTime()).thenReturn(Optional.of(now)); - Assert.assertEquals(now, delegatingFolder.creationTime().get()); - } - - @Test - public void testSetCreationTime() { - Folder mockFolder = Mockito.mock(Folder.class); - DelegatingFolder delegatingFolder = new TestDelegatingFolder(null, mockFolder); - - Instant now = Instant.now(); - delegatingFolder.setCreationTime(now); - Mockito.verify(mockFolder).setCreationTime(now); - } - - @Test - public void testChildren() { - Folder mockFolder = Mockito.mock(Folder.class); - TestDelegatingFileSystem delegatingFolder = TestDelegatingFileSystem.withRoot(mockFolder); - - Folder subFolder1 = Mockito.mock(Folder.class); - TestDelegatingFolder delegatingSubFolder1 = new TestDelegatingFolder(delegatingFolder, subFolder1); - File subFile1 = Mockito.mock(File.class); - TestDelegatingFile delegatingSubFile1 = new TestDelegatingFile(delegatingFolder, subFile1); - - /* folders */ - Mockito.when(mockFolder.folder("subFolder1")).thenReturn(subFolder1); - Assert.assertEquals(delegatingSubFolder1, delegatingFolder.folder("subFolder1")); - - Mockito.>when(mockFolder.folders()).thenAnswer((invocation) -> { - return Arrays.stream(new Folder[] {subFolder1}); - }); - List subFolders = delegatingFolder.folders().collect(Collectors.toList()); - Assert.assertThat(subFolders, Matchers.containsInAnyOrder(delegatingSubFolder1)); - - /* files */ - Mockito.when(mockFolder.file("subFile1")).thenReturn(subFile1); - Assert.assertEquals(delegatingSubFile1, delegatingFolder.file("subFile1")); - - Mockito.>when(mockFolder.files()).thenAnswer((invocation) -> { - return Arrays.stream(new File[] {subFile1}); - }); - List subFiles = delegatingFolder.files().collect(Collectors.toList()); - Assert.assertThat(subFiles, Matchers.containsInAnyOrder(delegatingSubFile1)); - - /* files and folders */ - List children = delegatingFolder.children().collect(Collectors.toList()); - DelegatingNode[] expectedChildren = new DelegatingNode[] {delegatingSubFolder1, delegatingSubFile1}; - Assert.assertThat(children, Matchers.containsInAnyOrder(expectedChildren)); - - } - - @Test - public void testMoveTo() { - Folder mockFolder1 = Mockito.mock(Folder.class); - Folder mockFolder2 = Mockito.mock(Folder.class); - DelegatingFolder delegatingFolder1 = new TestDelegatingFolder(null, mockFolder1); - DelegatingFolder delegatingFolder2 = new TestDelegatingFolder(null, mockFolder2); - - delegatingFolder1.moveTo(delegatingFolder2); - Mockito.verify(mockFolder1).moveTo(mockFolder2); - } - - @Test(expected = IllegalArgumentException.class) - public void testMoveToDestinationFromDifferentLayer() { - Folder mockFolder1 = Mockito.mock(Folder.class); - Folder mockFolder2 = Mockito.mock(Folder.class); - DelegatingFolder delegatingFolder1 = new TestDelegatingFolder(null, mockFolder1); - - delegatingFolder1.moveTo(mockFolder2); - } - - @Test - public void testCopyTo() { - Folder mockFolder1 = Mockito.mock(Folder.class); - Folder mockFolder2 = Mockito.mock(Folder.class); - DelegatingFolder delegatingFolder1 = new TestDelegatingFolder(null, mockFolder1); - DelegatingFolder delegatingFolder2 = new TestDelegatingFolder(null, mockFolder2); - - delegatingFolder1.copyTo(delegatingFolder2); - Mockito.verify(mockFolder1).copyTo(mockFolder2); - } - - @Test(expected = IllegalArgumentException.class) - public void testCopyToDestinationFromDifferentLayer() { - Folder mockFolder1 = Mockito.mock(Folder.class); - Folder mockFolder2 = Mockito.mock(Folder.class); - DelegatingFolder delegatingFolder1 = new TestDelegatingFolder(null, mockFolder1); - - delegatingFolder1.copyTo(mockFolder2); - } - - @Test - public void testCreate() { - Folder mockFolder = Mockito.mock(Folder.class); - DelegatingFolder delegatingFolder = new TestDelegatingFolder(null, mockFolder); - - delegatingFolder.create(); - Mockito.verify(mockFolder).create(); - } - - @Test - public void testDelete() { - Folder mockFolder = Mockito.mock(Folder.class); - DelegatingFolder delegatingFolder = new TestDelegatingFolder(null, mockFolder); - - delegatingFolder.delete(); - Mockito.verify(mockFolder).delete(); - } - - @Test - public void testSubresourcesAreSameInstance() { - Folder mockFolder = Mockito.mock(Folder.class); - Folder mockSubFolder = Mockito.mock(Folder.class); - File mockSubFile = Mockito.mock(File.class); - Mockito.when(mockFolder.folder("mockSubFolder")).thenReturn(mockSubFolder); - Mockito.when(mockFolder.file("mockSubFile")).thenReturn(mockSubFile); - - DelegatingFolder delegatingFolder = new TestDelegatingFolder(null, mockFolder); - Assert.assertSame(delegatingFolder.folder("mockSubFolder"), delegatingFolder.folder("mockSubFolder")); - Assert.assertSame(delegatingFolder.file("mockSubFile"), delegatingFolder.file("mockSubFile")); - } -} diff --git a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingReadableFileTest.java b/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingReadableFileTest.java deleted file mode 100644 index cd3fb106a..000000000 --- a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingReadableFileTest.java +++ /dev/null @@ -1,64 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 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.delegating; - -import java.nio.ByteBuffer; - -import org.cryptomator.filesystem.ReadableFile; -import org.junit.Assert; -import org.junit.Test; -import org.mockito.Mockito; - -public class DelegatingReadableFileTest { - - @Test - public void testIsOpen() { - ReadableFile mockReadableFile = Mockito.mock(ReadableFile.class); - @SuppressWarnings("resource") - DelegatingReadableFile delegatingReadableFile = new DelegatingReadableFile(mockReadableFile); - - Mockito.when(mockReadableFile.isOpen()).thenReturn(true); - Assert.assertTrue(delegatingReadableFile.isOpen()); - - Mockito.when(mockReadableFile.isOpen()).thenReturn(false); - Assert.assertFalse(delegatingReadableFile.isOpen()); - } - - @Test - public void testRead() { - ReadableFile mockReadableFile = Mockito.mock(ReadableFile.class); - @SuppressWarnings("resource") - DelegatingReadableFile delegatingReadableFile = new DelegatingReadableFile(mockReadableFile); - - ByteBuffer buf = ByteBuffer.allocate(4); - Mockito.when(mockReadableFile.read(buf)).thenReturn(4); - Assert.assertEquals(4, delegatingReadableFile.read(buf)); - Mockito.verify(mockReadableFile).read(buf); - } - - @Test - public void testPosition() { - ReadableFile mockReadableFile = Mockito.mock(ReadableFile.class); - @SuppressWarnings("resource") - DelegatingReadableFile delegatingReadableFile = new DelegatingReadableFile(mockReadableFile); - - delegatingReadableFile.position(42); - Mockito.verify(mockReadableFile).position(42); - } - - @Test - public void testClose() { - ReadableFile mockReadableFile = Mockito.mock(ReadableFile.class); - DelegatingReadableFile delegatingReadableFile = new DelegatingReadableFile(mockReadableFile); - - delegatingReadableFile.close(); - Mockito.verify(mockReadableFile).close(); - } - -} diff --git a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingWritableFileTest.java b/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingWritableFileTest.java deleted file mode 100644 index 67aa3a1b9..000000000 --- a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingWritableFileTest.java +++ /dev/null @@ -1,74 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 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.delegating; - -import java.nio.ByteBuffer; - -import org.cryptomator.filesystem.WritableFile; -import org.junit.Assert; -import org.junit.Test; -import org.mockito.Mockito; - -public class DelegatingWritableFileTest { - - @Test - public void testIsOpen() { - WritableFile mockWritableFile = Mockito.mock(WritableFile.class); - @SuppressWarnings("resource") - DelegatingWritableFile delegatingWritableFile = new DelegatingWritableFile(mockWritableFile); - - Mockito.when(mockWritableFile.isOpen()).thenReturn(true); - Assert.assertTrue(delegatingWritableFile.isOpen()); - - Mockito.when(mockWritableFile.isOpen()).thenReturn(false); - Assert.assertFalse(delegatingWritableFile.isOpen()); - } - - @Test - public void testTruncate() { - WritableFile mockWritableFile = Mockito.mock(WritableFile.class); - @SuppressWarnings("resource") - DelegatingWritableFile delegatingWritableFile = new DelegatingWritableFile(mockWritableFile); - - delegatingWritableFile.truncate(); - Mockito.verify(mockWritableFile).truncate(); - } - - @Test - public void testWrite() { - WritableFile mockWritableFile = Mockito.mock(WritableFile.class); - @SuppressWarnings("resource") - DelegatingWritableFile delegatingWritableFile = new DelegatingWritableFile(mockWritableFile); - - ByteBuffer buf = ByteBuffer.allocate(4); - Mockito.when(mockWritableFile.write(buf)).thenReturn(4); - Assert.assertEquals(4, delegatingWritableFile.write(buf)); - Mockito.verify(mockWritableFile).write(buf); - } - - @Test - public void testPosition() { - WritableFile mockWritableFile = Mockito.mock(WritableFile.class); - @SuppressWarnings("resource") - DelegatingWritableFile delegatingWritableFile = new DelegatingWritableFile(mockWritableFile); - - delegatingWritableFile.position(42); - Mockito.verify(mockWritableFile).position(42); - } - - @Test - public void testClose() { - WritableFile mockWritableFile = Mockito.mock(WritableFile.class); - DelegatingWritableFile delegatingWritableFile = new DelegatingWritableFile(mockWritableFile); - - delegatingWritableFile.close(); - Mockito.verify(mockWritableFile).close(); - } - -} diff --git a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/TestDelegatingFile.java b/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/TestDelegatingFile.java deleted file mode 100644 index 5ea445c9b..000000000 --- a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/TestDelegatingFile.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.cryptomator.filesystem.delegating; - -import org.cryptomator.filesystem.File; - -class TestDelegatingFile extends DelegatingFile { - - public TestDelegatingFile(TestDelegatingFolder parent, File delegate) { - super(parent, delegate); - } - -} diff --git a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/TestDelegatingFileSystem.java b/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/TestDelegatingFileSystem.java deleted file mode 100644 index 2a2ef3f6e..000000000 --- a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/TestDelegatingFileSystem.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.cryptomator.filesystem.delegating; - -import org.cryptomator.filesystem.Folder; - -class TestDelegatingFileSystem extends TestDelegatingFolder implements DelegatingFileSystem { - - private TestDelegatingFileSystem(Folder delegate) { - super(null, delegate); - } - - public static TestDelegatingFileSystem withRoot(Folder delegate) { - return new TestDelegatingFileSystem(delegate); - } - - @Override - public Folder getDelegate() { - return delegate; - } - -} diff --git a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/TestDelegatingFolder.java b/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/TestDelegatingFolder.java deleted file mode 100644 index 26bb6fe9f..000000000 --- a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/TestDelegatingFolder.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.cryptomator.filesystem.delegating; - -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.Folder; - -class TestDelegatingFolder extends DelegatingFolder { - - public TestDelegatingFolder(TestDelegatingFolder parent, Folder delegate) { - super(parent, delegate); - } - - @Override - protected TestDelegatingFile newFile(File delegate) { - return new TestDelegatingFile(this, delegate); - } - - @Override - protected TestDelegatingFolder newFolder(Folder delegate) { - return new TestDelegatingFolder(this, delegate); - } - -} diff --git a/main/filesystem-api/src/test/java/org/cryptomator/io/ByteBuffersTest.java b/main/filesystem-api/src/test/java/org/cryptomator/io/ByteBuffersTest.java deleted file mode 100644 index 8019f0789..000000000 --- a/main/filesystem-api/src/test/java/org/cryptomator/io/ByteBuffersTest.java +++ /dev/null @@ -1,81 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 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.io; - -import java.nio.ByteBuffer; - -import org.junit.Assert; -import org.junit.Test; - -public class ByteBuffersTest { - - @Test - public void testCopyOfEmptySource() { - final ByteBuffer src = ByteBuffer.allocate(0); - final ByteBuffer dst = ByteBuffer.allocate(5); - dst.put(new byte[3]); - Assert.assertEquals(0, src.position()); - Assert.assertEquals(0, src.remaining()); - Assert.assertEquals(3, dst.position()); - Assert.assertEquals(2, dst.remaining()); - ByteBuffers.copy(src, dst); - Assert.assertEquals(0, src.position()); - Assert.assertEquals(0, src.remaining()); - Assert.assertEquals(3, dst.position()); - Assert.assertEquals(2, dst.remaining()); - } - - @Test - public void testCopyToEmptyDestination() { - final ByteBuffer src = ByteBuffer.wrap(new byte[4]); - final ByteBuffer dst = ByteBuffer.allocate(0); - src.put(new byte[2]); - Assert.assertEquals(2, src.position()); - Assert.assertEquals(2, src.remaining()); - Assert.assertEquals(0, dst.position()); - Assert.assertEquals(0, dst.remaining()); - ByteBuffers.copy(src, dst); - Assert.assertEquals(2, src.position()); - Assert.assertEquals(2, src.remaining()); - Assert.assertEquals(0, dst.position()); - Assert.assertEquals(0, dst.remaining()); - } - - @Test - public void testCopyToBiggerDestination() { - final ByteBuffer src = ByteBuffer.wrap(new byte[2]); - final ByteBuffer dst = ByteBuffer.allocate(10); - dst.put(new byte[3]); - Assert.assertEquals(0, src.position()); - Assert.assertEquals(2, src.remaining()); - Assert.assertEquals(3, dst.position()); - Assert.assertEquals(7, dst.remaining()); - ByteBuffers.copy(src, dst); - Assert.assertEquals(2, src.position()); - Assert.assertEquals(0, src.remaining()); - Assert.assertEquals(5, dst.position()); - Assert.assertEquals(5, dst.remaining()); - } - - @Test - public void testCopyToSmallerDestination() { - final ByteBuffer src = ByteBuffer.wrap(new byte[5]); - final ByteBuffer dst = ByteBuffer.allocate(2); - Assert.assertEquals(0, src.position()); - Assert.assertEquals(5, src.remaining()); - Assert.assertEquals(0, dst.position()); - Assert.assertEquals(2, dst.remaining()); - ByteBuffers.copy(src, dst); - Assert.assertEquals(2, src.position()); - Assert.assertEquals(3, src.remaining()); - Assert.assertEquals(2, dst.position()); - Assert.assertEquals(0, dst.remaining()); - } - -} diff --git a/main/filesystem-api/src/test/java/org/cryptomator/io/FileContentsTest.java b/main/filesystem-api/src/test/java/org/cryptomator/io/FileContentsTest.java deleted file mode 100644 index 464a2d726..000000000 --- a/main/filesystem-api/src/test/java/org/cryptomator/io/FileContentsTest.java +++ /dev/null @@ -1,103 +0,0 @@ -package org.cryptomator.io; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; - -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.filesystem.WritableFile; -import org.junit.Assert; -import org.junit.Assume; -import org.junit.Test; -import org.junit.experimental.theories.DataPoints; -import org.junit.experimental.theories.Theories; -import org.junit.experimental.theories.Theory; -import org.junit.runner.RunWith; -import org.mockito.Mockito; - -@RunWith(Theories.class) -public class FileContentsTest { - - @DataPoints - public static final Iterable CHARSETS = Arrays.asList(StandardCharsets.UTF_8, StandardCharsets.US_ASCII, StandardCharsets.UTF_16); - - @DataPoints - public static final Iterable TEST_CONTENTS = Arrays.asList("hello world", "hellö wörld", ""); - - @Theory - public void testReadAll(Charset charset, String testString) { - Assume.assumeTrue(charset.newEncoder().canEncode(testString)); - - ByteBuffer testContent = ByteBuffer.wrap(testString.getBytes(charset)); - File file = Mockito.mock(File.class); - ReadableFile readable = Mockito.mock(ReadableFile.class); - Mockito.when(file.openReadable()).thenReturn(readable); - Mockito.when(readable.read(Mockito.any(ByteBuffer.class))).then(invocation -> { - ByteBuffer target = invocation.getArgumentAt(0, ByteBuffer.class); - if (testContent.hasRemaining()) { - return ByteBuffers.copy(testContent, target); - } else { - return -1; - } - }); - - String contentsRead = FileContents.withCharset(charset).readContents(file); - Assert.assertEquals(testString, contentsRead); - } - - @Theory - public void testWriteAll(Charset charset, String testString) { - Assume.assumeTrue(charset.newEncoder().canEncode(testString)); - - ByteBuffer testContent = ByteBuffer.allocate(100); - File file = Mockito.mock(File.class); - WritableFile writable = Mockito.mock(WritableFile.class); - Mockito.when(file.openWritable()).thenReturn(writable); - Mockito.doAnswer(invocation -> { - testContent.clear(); - return null; - }).when(writable).truncate(); - Mockito.when(writable.write(Mockito.any(ByteBuffer.class))).then(invocation -> { - ByteBuffer source = invocation.getArgumentAt(0, ByteBuffer.class); - if (testContent.hasRemaining()) { - return ByteBuffers.copy(source, testContent); - } else { - return -1; - } - }); - - FileContents.withCharset(charset).writeContents(file, testString); - Assert.assertArrayEquals(testString.getBytes(charset), Arrays.copyOf(testContent.array(), testContent.position())); - } - - @Test(expected = UncheckedIOException.class) - public void testIOExceptionDuringRead() { - File file = Mockito.mock(File.class); - Mockito.when(file.openReadable()).thenAnswer(invocation -> { - throw new IOException("failed"); - }); - - FileContents.UTF_8.readContents(file); - } - - @Test(expected = UncheckedIOException.class) - public void testUncheckedIOExceptionDuringRead() { - File file = Mockito.mock(File.class); - Mockito.when(file.openReadable()).thenThrow(new UncheckedIOException(new IOException("failed"))); - - FileContents.UTF_8.readContents(file); - } - - @Test(expected = UncheckedIOException.class) - public void testUncheckedIOExceptionDuringWrite() { - File file = Mockito.mock(File.class); - Mockito.when(file.openWritable()).thenThrow(new UncheckedIOException(new IOException("failed"))); - - FileContents.UTF_8.writeContents(file, "hello world"); - } - -} diff --git a/main/filesystem-api/src/test/resources/log4j2.xml b/main/filesystem-api/src/test/resources/log4j2.xml deleted file mode 100644 index 9b4889392..000000000 --- a/main/filesystem-api/src/test/resources/log4j2.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/main/filesystem-charsets/pom.xml b/main/filesystem-charsets/pom.xml deleted file mode 100644 index 817000f84..000000000 --- a/main/filesystem-charsets/pom.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - 4.0.0 - - org.cryptomator - main - 1.3.0-SNAPSHOT - - filesystem-charsets - Cryptomator filesystem: 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 deleted file mode 100644 index af73d59c1..000000000 --- a/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFile.java +++ /dev/null @@ -1,32 +0,0 @@ -/******************************************************************************* - * 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 deleted file mode 100644 index a69c572d6..000000000 --- a/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFileSystem.java +++ /dev/null @@ -1,27 +0,0 @@ -/******************************************************************************* - * 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 deleted file mode 100644 index d03c42d86..000000000 --- a/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFolder.java +++ /dev/null @@ -1,76 +0,0 @@ -/******************************************************************************* - * 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.debug("Ambiguous file names \"" + nfcName + "\" (NFC) vs. \"" + nfdName + "\" (NFD). Both files exist. Using \"" + nfcName + "\" (NFC)."); - } else if (!nfcName.equals(nfdName) && !nfcFile.exists() && nfdFile.exists()) { - LOG.debug("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.debug("Ambiguous folder names \"" + nfcName + "\" (NFC) vs. \"" + nfdName + "\" (NFD). Both files exist. Using \"" + nfcName + "\" (NFC)."); - } else if (!nfcName.equals(nfdName) && !nfcFolder.exists() && nfdFolder.exists()) { - LOG.debug("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 deleted file mode 100644 index 3ee4f6a17..000000000 --- a/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/package-info.java +++ /dev/null @@ -1,16 +0,0 @@ -/******************************************************************************* - * 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 deleted file mode 100644 index e662838a8..000000000 --- a/main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFileSystemTest.java +++ /dev/null @@ -1,90 +0,0 @@ -/******************************************************************************* - * 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 deleted file mode 100644 index 3a3f1fe64..000000000 --- a/main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFileTest.java +++ /dev/null @@ -1,48 +0,0 @@ -/******************************************************************************* - * 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 deleted file mode 100644 index 7bf017652..000000000 --- a/main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFolderTest.java +++ /dev/null @@ -1,149 +0,0 @@ -/******************************************************************************* - * 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 deleted file mode 100644 index 9b4889392..000000000 --- a/main/filesystem-charsets/src/test/resources/log4j2.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/main/filesystem-crypto-integration-tests/.gitignore b/main/filesystem-crypto-integration-tests/.gitignore deleted file mode 100644 index b83d22266..000000000 --- a/main/filesystem-crypto-integration-tests/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target/ diff --git a/main/filesystem-crypto-integration-tests/pom.xml b/main/filesystem-crypto-integration-tests/pom.xml deleted file mode 100644 index 0c732d6e0..000000000 --- a/main/filesystem-crypto-integration-tests/pom.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - - 4.0.0 - - org.cryptomator - main - 1.3.0-SNAPSHOT - - filesystem-crypto-integration-tests - Cryptomator filesystem: Encryption layer tests - - - - org.cryptomator - filesystem-api - - - org.cryptomator - filesystem-crypto - - - org.cryptomator - filesystem-nameshortening - - - - - com.google.dagger - dagger - - - com.google.dagger - dagger-compiler - provided - - - - - org.cryptomator - commons-test - - - org.cryptomator - filesystem-inmemory - - - - - - - org.jacoco - jacoco-maven-plugin - - - - \ No newline at end of file diff --git a/main/filesystem-crypto-integration-tests/src/main/java/org/cryptomator/filesystem/crypto/CryptoEngineTestModule.java b/main/filesystem-crypto-integration-tests/src/main/java/org/cryptomator/filesystem/crypto/CryptoEngineTestModule.java deleted file mode 100644 index 161666ce8..000000000 --- a/main/filesystem-crypto-integration-tests/src/main/java/org/cryptomator/filesystem/crypto/CryptoEngineTestModule.java +++ /dev/null @@ -1,33 +0,0 @@ -/******************************************************************************* - * 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.crypto; - -import java.security.SecureRandom; -import java.util.Arrays; - -import org.cryptomator.crypto.engine.impl.CryptoEngineModule; - -/** - * Used as drop-in-replacement for {@link CryptoEngineModule} during unit tests. - */ -public class CryptoEngineTestModule extends CryptoEngineModule { - - @Override - public SecureRandom provideSecureRandom() { - return new SecureRandom() { - - @Override - public void nextBytes(byte[] bytes) { - Arrays.fill(bytes, (byte) 0x00); - } - - }; - } - -} diff --git a/main/filesystem-crypto-integration-tests/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystemTestComponent.java b/main/filesystem-crypto-integration-tests/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystemTestComponent.java deleted file mode 100644 index 98b11e925..000000000 --- a/main/filesystem-crypto-integration-tests/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystemTestComponent.java +++ /dev/null @@ -1,32 +0,0 @@ -/******************************************************************************* - * 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.crypto; - -import javax.inject.Singleton; - -import org.cryptomator.crypto.engine.impl.CryptoEngineModule; -import org.cryptomator.filesystem.shortening.ShorteningFileSystemFactory; - -import dagger.Component; - -/** - * To be used in integration tests, where a {@link CryptoFileSystem} is needed in conjunction with {@link CryptoEngineTestModule} (which mocks the CSPRNG) as follows: - * - * DaggerCryptoFileSystemTestComponent.builder().cryptoEngineModule(new CryptoEngineTestModule()).build() - * - */ -@Singleton -@Component(modules = CryptoEngineModule.class) -public interface CryptoFileSystemTestComponent { - - CryptoFileSystemFactory cryptoFileSystemFactory(); - - ShorteningFileSystemFactory shorteningFileSystemFactory(); - -} diff --git a/main/filesystem-crypto-integration-tests/src/test/java/org/cryptomator/filesystem/crypto/CryptoFileSystemIntegrationTest.java b/main/filesystem-crypto-integration-tests/src/test/java/org/cryptomator/filesystem/crypto/CryptoFileSystemIntegrationTest.java deleted file mode 100644 index 9d8b26869..000000000 --- a/main/filesystem-crypto-integration-tests/src/test/java/org/cryptomator/filesystem/crypto/CryptoFileSystemIntegrationTest.java +++ /dev/null @@ -1,298 +0,0 @@ -/******************************************************************************* - * 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.crypto; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.ForkJoinTask; -import java.util.concurrent.Future; - -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.Node; -import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.filesystem.WritableFile; -import org.cryptomator.filesystem.inmem.InMemoryFileSystem; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class CryptoFileSystemIntegrationTest { - - private static final Logger LOG = LoggerFactory.getLogger(CryptoFileSystemIntegrationTest.class); - - private final CryptoFileSystemTestComponent cryptoFsComp = DaggerCryptoFileSystemTestComponent.builder().cryptoEngineModule(new CryptoEngineTestModule()).build(); - - private CryptoFileSystemDelegate cryptoDelegate; - private FileSystem ciphertextFs; - private FileSystem cleartextFs; - - @Before - public void setupFileSystems() { - cryptoDelegate = Mockito.mock(CryptoFileSystemDelegate.class); - ciphertextFs = new InMemoryFileSystem(); - FileSystem shorteningFs = cryptoFsComp.shorteningFileSystemFactory().get(ciphertextFs); - cryptoFsComp.cryptoFileSystemFactory().initializeNew(shorteningFs, "TopSecret"); - cleartextFs = cryptoFsComp.cryptoFileSystemFactory().unlockExisting(shorteningFs, "TopSecret", cryptoDelegate); - } - - @Test(timeout = 1000) - public void testVaultStructureInitializationAndBackupBehaviour() throws UncheckedIOException, IOException { - final FileSystem physicalFs = new InMemoryFileSystem(); - final File masterkeyFile = physicalFs.file("masterkey.cryptomator"); - final File masterkeyBkupFile = physicalFs.file("masterkey.cryptomator.bkup"); - final Folder physicalDataRoot = physicalFs.folder("d"); - Assert.assertFalse(masterkeyFile.exists()); - Assert.assertFalse(masterkeyBkupFile.exists()); - Assert.assertFalse(physicalDataRoot.exists()); - - cryptoFsComp.cryptoFileSystemFactory().initializeNew(physicalFs, "asd"); - Assert.assertTrue(masterkeyFile.exists()); - Assert.assertFalse(masterkeyBkupFile.exists()); - Assert.assertFalse(physicalDataRoot.exists()); - - @SuppressWarnings("unused") - final FileSystem cryptoFs = cryptoFsComp.cryptoFileSystemFactory().unlockExisting(physicalFs, "asd", cryptoDelegate); - Assert.assertTrue(masterkeyBkupFile.exists()); - Assert.assertTrue(physicalDataRoot.exists()); - Assert.assertEquals(3, physicalFs.children().count()); // d + masterkey.cryptomator + masterkey.cryptomator.bkup - Assert.assertEquals(1, physicalDataRoot.folders().count()); // ROOT directory - } - - @Test - public void testEncryptionOfLongFolderNames() { - final String shortName = "normal folder name"; - final String longName = "this will be a long filename after encryption, because its encrypted name is longer than onehundredandeighty characters"; - - final Folder shortFolder = cleartextFs.folder(shortName); - final Folder longFolder = cleartextFs.folder(longName); - - shortFolder.create(); - longFolder.create(); - - // because of the long file, a metadata folder should exist on the physical layer: - Assert.assertEquals(1, ciphertextFs.folder("m").folders().count()); - Assert.assertTrue(ciphertextFs.folder("m").exists()); - - // but the shortened filenames must not be visible on the cleartext layer: - Assert.assertArrayEquals(new String[] {shortName, longName}, cleartextFs.folders().map(Node::name).sorted().toArray()); - } - - @Test - public void testEncryptionAndDecryptionOfFiles() { - // write test content to encrypted file - try (WritableFile writable = cleartextFs.file("test1.txt").openWritable()) { - writable.write(ByteBuffer.wrap("Hello ".getBytes())); - writable.write(ByteBuffer.wrap("World".getBytes())); - } - - File physicalFile = ciphertextFs.folder("d").folders().findAny().get().folders().findAny().get().files().findAny().get(); - Assert.assertTrue(physicalFile.exists()); - - // read test content from decrypted file - try (ReadableFile readable = cleartextFs.file("test1.txt").openReadable()) { - ByteBuffer buf1 = ByteBuffer.allocate(5); - readable.read(buf1); - buf1.flip(); - Assert.assertEquals("Hello", new String(buf1.array(), 0, buf1.remaining())); - ByteBuffer buf2 = ByteBuffer.allocate(10); - readable.read(buf2); - buf2.flip(); - Assert.assertArrayEquals(" World".getBytes(), Arrays.copyOfRange(buf2.array(), 0, buf2.remaining())); - } - } - - @Test - public void testForcedDecryptionOfManipulatedFile() { - // write test content to encrypted file - try (WritableFile writable = cleartextFs.file("test1.txt").openWritable()) { - writable.write(ByteBuffer.wrap("Hello World".getBytes())); - } - - File physicalFile = ciphertextFs.folder("d").folders().findAny().get().folders().findAny().get().files().findAny().get(); - Assert.assertTrue(physicalFile.exists()); - - // toggle last bit - try (WritableFile writable = physicalFile.openWritable(); ReadableFile readable = physicalFile.openReadable()) { - ByteBuffer buf = ByteBuffer.allocate((int) physicalFile.size()); - readable.read(buf); - buf.array()[buf.limit() - 1] ^= 0x01; - buf.flip(); - writable.write(buf); - } - - // whitelist - Mockito.when(cryptoDelegate.shouldSkipAuthentication("/test1.txt")).thenReturn(true); - - // read test content from decrypted file - try (ReadableFile readable = cleartextFs.file("test1.txt").openReadable()) { - ByteBuffer buf = ByteBuffer.allocate(11); - readable.read(buf); - buf.flip(); - Assert.assertArrayEquals("Hello World".getBytes(), buf.array()); - } - } - - @Test(timeout = 20000) // assuming a minimum speed of 10mb/s during encryption and decryption 20s should be enough - public void testEncryptionAndDecryptionSpeed() throws InterruptedException, IOException { - File file = cleartextFs.file("benchmark.test"); - - final long encStart = System.nanoTime(); - try (WritableFile writable = file.openWritable()) { - final ByteBuffer cleartext = ByteBuffer.allocate(100000); // 100k - for (int i = 0; i < 1000; i++) { // 100M total - cleartext.rewind(); - writable.write(cleartext); - } - } - final long encEnd = System.nanoTime(); - LOG.debug("Encryption of 100M took {}ms", (encEnd - encStart) / 1000 / 1000); - - final long decStart = System.nanoTime(); - try (ReadableFile readable = file.openReadable()) { - final ByteBuffer cleartext = ByteBuffer.allocate(100000); // 100k - for (int i = 0; i < 1000; i++) { // 100M total - cleartext.clear(); - readable.read(cleartext); - cleartext.flip(); - Assert.assertEquals(cleartext.get(), 0x00); - } - } - final long decEnd = System.nanoTime(); - LOG.debug("Decryption of 100M took {}ms", (decEnd - decStart) / 1000 / 1000); - - file.delete(); - } - - @Test - public void testRandomAccessOnLastBlock() { - // prepare test data: - ByteBuffer testData = ByteBuffer.allocate(16000 * Integer.BYTES); // < 64kb - for (int i = 0; i < 16000; i++) { - testData.putInt(i); - } - - // write test data to file: - File cleartextFile = cleartextFs.file("test"); - try (WritableFile writable = cleartextFile.openWritable()) { - testData.flip(); - writable.write(testData); - } - - // read last block: - try (ReadableFile readable = cleartextFile.openReadable()) { - ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES); - buf.clear(); - readable.position(15999 * Integer.BYTES); - readable.read(buf); - buf.flip(); - Assert.assertEquals(15999, buf.getInt()); - } - } - - @Test - public void testSequentialRandomAccess() { - // prepare test data: - ByteBuffer testData = ByteBuffer.allocate(1_000_000 * Integer.BYTES); // = 4MB - for (int i = 0; i < 1000000; i++) { - testData.putInt(i); - } - - // write test data to file: - File cleartextFile = cleartextFs.file("test"); - try (WritableFile writable = cleartextFile.openWritable()) { - testData.flip(); - writable.write(testData); - } - - // shuffle our test positions: - List nums = new ArrayList<>(); - for (int i = 0; i < 1_000_000; i++) { - nums.add(i); - } - Collections.shuffle(nums); - - // read parts from positions: - try (ReadableFile readable = cleartextFile.openReadable()) { - ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES); - for (int i = 0; i < 1000; i++) { - int num = nums.get(i); - buf.clear(); - readable.position(num * Integer.BYTES); - readable.read(buf); - buf.flip(); - Assert.assertEquals(num, buf.getInt()); - } - } - } - - @Test - public void testParallelRandomAccess() { - // prepare test data: - ByteBuffer testData = ByteBuffer.allocate(1_000_000 * Integer.BYTES); // = 4MB - for (int i = 0; i < 1000000; i++) { - testData.putInt(i); - } - - // write test data to file: - final File cleartextFile = cleartextFs.file("test"); - try (WritableFile writable = cleartextFile.openWritable()) { - testData.flip(); - writable.write(testData); - } - - // shuffle our test positions: - List nums = new ArrayList<>(); - for (int i = 0; i < 1_000_000; i++) { - nums.add(i); - } - Collections.shuffle(nums); - - // read parts from positions in parallel: - final ForkJoinPool pool = new ForkJoinPool(10); - final List> tasks = new ArrayList<>(); - for (int i = 0; i < 1000; i++) { - final int num = nums.get(i); - final ForkJoinTask task = ForkJoinTask.adapt(() -> { - try (ReadableFile readable = cleartextFile.openReadable()) { - ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES); - buf.clear(); - readable.position(num * Integer.BYTES); - readable.read(buf); - buf.flip(); - int numRead = buf.getInt(); - return num == numRead; - } - }); - pool.execute(task); - tasks.add(task); - } - - // Wait for tasks to finish and check results - Assert.assertTrue(tasks.stream().allMatch(task -> { - try { - return task.get(); - } catch (Exception e) { - e.printStackTrace(); - return false; - } - })); - } - -} diff --git a/main/filesystem-crypto-integration-tests/src/test/resources/log4j2.xml b/main/filesystem-crypto-integration-tests/src/test/resources/log4j2.xml deleted file mode 100644 index 9b4889392..000000000 --- a/main/filesystem-crypto-integration-tests/src/test/resources/log4j2.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/main/filesystem-crypto/.gitignore b/main/filesystem-crypto/.gitignore deleted file mode 100644 index b83d22266..000000000 --- a/main/filesystem-crypto/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target/ diff --git a/main/filesystem-crypto/pom.xml b/main/filesystem-crypto/pom.xml deleted file mode 100644 index 209f404c0..000000000 --- a/main/filesystem-crypto/pom.xml +++ /dev/null @@ -1,93 +0,0 @@ - - - - 4.0.0 - - org.cryptomator - main - 1.3.0-SNAPSHOT - - filesystem-crypto - Cryptomator filesystem: Encryption layer - - - 1.51 - 1.2.0 - - - - - org.cryptomator - filesystem-api - - - org.cryptomator - commons - - - - - org.cryptomator - siv-mode - ${sivmode.version} - - - org.bouncycastle - bcprov-jdk15on - ${bouncycastle.version} - - - - - org.apache.commons - commons-lang3 - - - commons-codec - commons-codec - - - - - com.fasterxml.jackson.core - jackson-databind - - - - - com.google.dagger - dagger - - - com.google.dagger - dagger-compiler - provided - - - - - org.cryptomator - commons-test - - - org.cryptomator - filesystem-inmemory - - - - - - - org.jacoco - jacoco-maven-plugin - - - - \ No newline at end of file diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/AuthenticationFailedException.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/AuthenticationFailedException.java deleted file mode 100644 index 0b350cc67..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/AuthenticationFailedException.java +++ /dev/null @@ -1,29 +0,0 @@ -/******************************************************************************* - * 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.crypto.engine; - -public class AuthenticationFailedException extends CryptoException { - - public AuthenticationFailedException() { - super(); - } - - public AuthenticationFailedException(String message) { - super(message); - } - - public AuthenticationFailedException(Throwable cause) { - super(cause); - } - - public AuthenticationFailedException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/CryptoException.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/CryptoException.java deleted file mode 100644 index f31431003..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/CryptoException.java +++ /dev/null @@ -1,29 +0,0 @@ -/******************************************************************************* - * 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.crypto.engine; - -public abstract class CryptoException extends RuntimeException { - - public CryptoException() { - super(); - } - - public CryptoException(String message) { - super(message); - } - - public CryptoException(Throwable cause) { - super(cause); - } - - public CryptoException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/Cryptor.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/Cryptor.java deleted file mode 100644 index 514365008..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/Cryptor.java +++ /dev/null @@ -1,31 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto.engine; - -import javax.security.auth.Destroyable; - -/** - * A Cryptor instance, once initialized with a set of keys, provides access to threadsafe cryptographic routines. - */ -public interface Cryptor extends Destroyable { - - FilenameCryptor getFilenameCryptor(); - - FileContentCryptor getFileContentCryptor(); - - void randomizeMasterkey(); - - void readKeysFromMasterkeyFile(byte[] masterkeyFileContents, CharSequence passphrase) throws InvalidPassphraseException, UnsupportedVaultFormatException; - - byte[] writeKeysToMasterkeyFile(CharSequence passphrase); - - @Override - void destroy(); - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentCryptor.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentCryptor.java deleted file mode 100644 index ec151a314..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentCryptor.java +++ /dev/null @@ -1,49 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto.engine; - -import java.nio.ByteBuffer; -import java.util.Optional; - -/** - * Factory for stateful {@link FileContentEncryptor Encryptor}/{@link FileContentDecryptor Decryptor} instances, that are capable of processing data exactly once. - */ -public interface FileContentCryptor { - - public static final ByteBuffer EOF = ByteBuffer.allocate(0); - - /** - * @return The fixed number of bytes of the file header. The header length is implementation-specific. - */ - int getHeaderSize(); - - /** - * @return The ciphertext position that correlates to the cleartext position. - */ - long toCiphertextPos(long cleartextPos); - - /** - * @param header The full fixed-length header of an encrypted file. The caller is required to pass the exact amount of bytes returned by {@link #getHeaderSize()}. - * @param firstCiphertextByte Position of the first ciphertext byte passed to the decryptor. If the decryptor can not fast-forward to the requested byte, an exception is thrown. - * If firstCiphertextByte is an invalid starting point, i.e. doesn't align with the decryptors internal block size, an IllegalArgumentException will be thrown. - * @param authenticate Skip authentication by setting this flag to false. Should be true by default. - * @return A possibly new FileContentDecryptor instance which is capable of decrypting ciphertexts associated with the given file header. - */ - FileContentDecryptor createFileContentDecryptor(ByteBuffer header, long firstCiphertextByte, boolean authenticate) throws IllegalArgumentException, AuthenticationFailedException; - - /** - * @param header The full fixed-length header of an encrypted file or {@link Optional#empty()}. The caller is required to pass the exact amount of bytes returned by {@link #getHeaderSize()}. - * If the header is empty, a new one will be created by the returned encryptor. - * @param firstCleartextByte Position of the first cleartext byte passed to the encryptor. If the encryptor can not fast-forward to the requested byte, an exception is thrown. - * If firstCiphertextByte is an invalid starting point, i.e. doesn't align with the encryptors internal block size, an IllegalArgumentException will be thrown. - * @return A possibly new FileContentEncryptor instance which is capable of encrypting cleartext associated with the given file header. - */ - FileContentEncryptor createFileContentEncryptor(Optional header, long firstCleartextByte) throws IllegalArgumentException; - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentDecryptor.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentDecryptor.java deleted file mode 100644 index be7c468a9..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentDecryptor.java +++ /dev/null @@ -1,61 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto.engine; - -import java.io.Closeable; -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; - -import javax.security.auth.Destroyable; - -/** - * Stateful, thus not thread-safe. - */ -public interface FileContentDecryptor extends Destroyable, Closeable { - - /** - * Appends further ciphertext to this decryptor. This method might block until space becomes available. If so, it is interruptable. - * - * @param cleartext Cleartext data or {@link FileContentCryptor#EOF} to indicate the end of a ciphertext. - * @see #skipToPosition(long) - */ - void append(ByteBuffer ciphertext) throws InterruptedException; - - /** - * Cancels decryption due to an exception in the thread responsible for appending ciphertext. - * The exception will be the root cause of an {@link UncheckedIOException} thrown by {@link #cleartext()} when retrieving the decrypted result. - * - * @param cause The exception making it impossible to {@link #append(ByteBuffer)} further ciphertext. - */ - void cancelWithException(Exception cause) throws InterruptedException; - - /** - * Returns the next decrypted cleartext in byte-by-byte FIFO order, meaning in the order ciphertext has been appended to this encryptor. - * However the number and size of the cleartext byte buffers doesn't need to resemble the ciphertext buffers. - * - * This method might block if no cleartext is available yet. - * - * @return Decrypted cleartext or {@link FileContentCryptor#EOF}. - * @throws AuthenticationFailedException On MAC mismatches - * @throws UncheckedIOException In case of I/O exceptions, e.g. caused by previous {@link #cancelWithException(Exception)}. - */ - ByteBuffer cleartext() throws InterruptedException, AuthenticationFailedException, UncheckedIOException; - - /** - * Clears file-specific sensitive information. - */ - @Override - void destroy(); - - @Override - default void close() { - this.destroy(); - } - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentEncryptor.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentEncryptor.java deleted file mode 100644 index 614941641..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentEncryptor.java +++ /dev/null @@ -1,72 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto.engine; - -import java.io.Closeable; -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; - -import javax.security.auth.Destroyable; - -/** - * Stateful, thus not thread-safe. - */ -public interface FileContentEncryptor extends Destroyable, Closeable { - - /** - * Creates the encrypted file header. This header might depend on the already encrypted data, - * thus the caller should make sure all data is processed before requesting the header. - * - * @return Encrypted file header. - */ - ByteBuffer getHeader(); - - /** - * @return the size of headers created by this {@code FileContentCryptor}. The length of headers returned by {@link #getHeader()} equals this value. - */ - int getHeaderSize(); - - /** - * Appends further cleartext to this encryptor. This method might block until space becomes available. - * - * @param cleartext Cleartext data or {@link FileContentCryptor#EOF} to indicate the end of a cleartext. - */ - void append(ByteBuffer cleartext) throws InterruptedException; - - /** - * Cancels encryption due to an exception in the thread responsible for appending cleartext. - * The exception will be the root cause of an {@link UncheckedIOException} thrown by {@link #ciphertext()} when retrieving the encrypted result. - * - * @param cause The exception making it impossible to {@link #append(ByteBuffer)} further cleartext. - */ - void cancelWithException(Exception cause) throws InterruptedException; - - /** - * Returns the next ciphertext in byte-by-byte FIFO order, meaning in the order cleartext has been appended to this encryptor. - * However the number and size of the ciphertext byte buffers doesn't need to resemble the cleartext buffers. - * - * This method might block if no ciphertext is available yet. - * - * @return Encrypted ciphertext of {@link FileContentCryptor#EOF}. - * @throws UncheckedIOException In case of I/O exceptions, e.g. caused by previous {@link #cancelWithException(Exception)}. - */ - ByteBuffer ciphertext() throws InterruptedException, UncheckedIOException; - - /** - * Clears file-specific sensitive information. - */ - @Override - void destroy(); - - @Override - default void close() { - this.destroy(); - } - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FilenameCryptor.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FilenameCryptor.java deleted file mode 100644 index 1226da4ed..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FilenameCryptor.java +++ /dev/null @@ -1,44 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto.engine; - -import java.util.regex.Pattern; - -/** - * Provides deterministic encryption capabilities as filenames must not change on subsequent encryption attempts, - * otherwise each change results in major directory structure changes which would be a terrible idea for cloud storage encryption. - * - * @see Wikipedia on deterministic encryption - */ -public interface FilenameCryptor { - - /** - * @return constant length string, that is unlikely to collide with any other name. - */ - String hashDirectoryId(String cleartextDirectoryId); - - /** - * @return A Pattern that can be used to test, if a name is a well-formed ciphertext. - */ - Pattern encryptedNamePattern(); - - /** - * @param cleartextName original filename including cleartext file extension - * @param associatedData optional associated data, that will not get encrypted but needs to be provided during decryption - * @return encrypted filename without any file extension - */ - String encryptFilename(String cleartextName, byte[]... associatedData); - - /** - * @param ciphertextName Ciphertext only, with any additional strings like file extensions stripped first. - * @param associatedData the same associated data used during encryption, otherwise and {@link AuthenticationFailedException} will be thrown - * @return cleartext filename, probably including its cleartext file extension. - */ - String decryptFilename(String ciphertextName, byte[]... associatedData) throws AuthenticationFailedException; -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/InvalidPassphraseException.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/InvalidPassphraseException.java deleted file mode 100644 index 3518cc150..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/InvalidPassphraseException.java +++ /dev/null @@ -1,17 +0,0 @@ -/******************************************************************************* - * 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.crypto.engine; - -public class InvalidPassphraseException extends CryptoException { - - public InvalidPassphraseException() { - super(); - } - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/UnsupportedVaultFormatException.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/UnsupportedVaultFormatException.java deleted file mode 100644 index 1f087b080..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/UnsupportedVaultFormatException.java +++ /dev/null @@ -1,38 +0,0 @@ -/******************************************************************************* - * 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.crypto.engine; - -public class UnsupportedVaultFormatException extends CryptoException { - - private final Integer detectedVersion; - private final Integer latestSupportedVersion; - - public UnsupportedVaultFormatException(Integer detectedVersion, Integer latestSupportedVersion) { - super("Tried to open vault of version " + detectedVersion + ", latest supported version is " + latestSupportedVersion); - this.detectedVersion = detectedVersion; - this.latestSupportedVersion = latestSupportedVersion; - } - - public Integer getDetectedVersion() { - return detectedVersion; - } - - public Integer getLatestSupportedVersion() { - return latestSupportedVersion; - } - - public boolean isVaultOlderThanSoftware() { - return detectedVersion == null || detectedVersion < latestSupportedVersion; - } - - public boolean isSoftwareOlderThanVault() { - return detectedVersion > latestSupportedVersion; - } - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/AesKeyWrap.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/AesKeyWrap.java deleted file mode 100644 index de5521223..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/AesKeyWrap.java +++ /dev/null @@ -1,71 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 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.crypto.engine.impl; - -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; - -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; - -final class AesKeyWrap { - - private static final String RFC3394_CIPHER = "AESWrap"; - - private AesKeyWrap() { - } - - /** - * @param kek Key encrypting key - * @param key Key to be wrapped - * @return Wrapped key - */ - public static byte[] wrap(SecretKey kek, SecretKey key) { - final Cipher cipher; - try { - cipher = Cipher.getInstance(RFC3394_CIPHER); - cipher.init(Cipher.WRAP_MODE, kek); - } catch (InvalidKeyException e) { - throw new IllegalArgumentException("Invalid key.", e); - } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { - throw new IllegalStateException("Algorithm/Padding should exist.", e); - } - - try { - return cipher.wrap(key); - } catch (InvalidKeyException | IllegalBlockSizeException e) { - throw new IllegalStateException("Unable to wrap key.", e); - } - } - - /** - * @param kek Key encrypting key - * @param wrappedKey Key to be unwrapped - * @param keyAlgorithm Key designation, i.e. algorithm name to be associated with the unwrapped key. - * @return Unwrapped key - * @throws NoSuchAlgorithmException If keyAlgorithm is unknown - * @throws InvalidKeyException If unwrapping failed (i.e. wrong kek) - */ - public static SecretKey unwrap(SecretKey kek, byte[] wrappedKey, String keyAlgorithm) throws InvalidKeyException, NoSuchAlgorithmException { - final Cipher cipher; - try { - cipher = Cipher.getInstance(RFC3394_CIPHER); - cipher.init(Cipher.UNWRAP_MODE, kek); - } catch (InvalidKeyException ex) { - throw new IllegalArgumentException("Invalid key.", ex); - } catch (NoSuchAlgorithmException | NoSuchPaddingException ex) { - throw new IllegalStateException("Algorithm/Padding should exist.", ex); - } - - return (SecretKey) cipher.unwrap(wrappedKey, keyAlgorithm, Cipher.SECRET_KEY); - } - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Constants.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Constants.java deleted file mode 100644 index 218f4c3aa..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Constants.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.cryptomator.crypto.engine.impl; - -public final class Constants { - - private Constants() { - } - - static final Integer CURRENT_VAULT_VERSION = 5; - - public static final int PAYLOAD_SIZE = 32 * 1024; - public static final int NONCE_SIZE = 16; - public static final int MAC_SIZE = 32; - public static final int CHUNK_SIZE = NONCE_SIZE + PAYLOAD_SIZE + MAC_SIZE; - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/CryptoEngineModule.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/CryptoEngineModule.java deleted file mode 100644 index fc336c7c1..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/CryptoEngineModule.java +++ /dev/null @@ -1,41 +0,0 @@ -/******************************************************************************* - * 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.crypto.engine.impl; - -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; - -import org.cryptomator.crypto.engine.Cryptor; - -import dagger.Module; -import dagger.Provides; - -@Module -public class CryptoEngineModule { - - @Provides - public Cryptor provideCryptor(SecureRandom secureRandom) { - return new CryptorImpl(secureRandom); - } - - @Provides - public SecureRandom provideSecureRandom() { - try { - // https://tersesystems.com/2015/12/17/the-right-way-to-use-securerandom/ - final SecureRandom nativeRandom = SecureRandom.getInstanceStrong(); - byte[] seed = nativeRandom.generateSeed(55); // NIST SP800-90A suggests 440 bits for SHA1 seed - SecureRandom sha1Random = SecureRandom.getInstance("SHA1PRNG"); - sha1Random.setSeed(seed); - return sha1Random; - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("No strong PRNGs available.", e); - } - } - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/CryptorImpl.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/CryptorImpl.java deleted file mode 100644 index ef88c4a8d..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/CryptorImpl.java +++ /dev/null @@ -1,197 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto.engine.impl; - -import static org.cryptomator.crypto.engine.impl.Constants.CURRENT_VAULT_VERSION; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.Arrays; -import java.util.concurrent.atomic.AtomicReference; - -import javax.crypto.KeyGenerator; -import javax.crypto.Mac; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import javax.security.auth.DestroyFailedException; -import javax.security.auth.Destroyable; - -import org.cryptomator.common.LazyInitializer; -import org.cryptomator.crypto.engine.Cryptor; -import org.cryptomator.crypto.engine.FileContentCryptor; -import org.cryptomator.crypto.engine.FilenameCryptor; -import org.cryptomator.crypto.engine.InvalidPassphraseException; -import org.cryptomator.crypto.engine.UnsupportedVaultFormatException; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.exc.InvalidFormatException; - -class CryptorImpl implements Cryptor { - - private static final int SCRYPT_SALT_LENGTH = 8; - private static final int SCRYPT_COST_PARAM = 1 << 14; - private static final int SCRYPT_BLOCK_SIZE = 8; - private static final int KEYLENGTH_IN_BYTES = 32; - private static final String ENCRYPTION_ALG = "AES"; - private static final String MAC_ALG = "HmacSHA256"; - - private SecretKey encryptionKey; - private SecretKey macKey; - private final AtomicReference filenameCryptor = new AtomicReference<>(); - private final AtomicReference fileContentCryptor = new AtomicReference<>(); - private final SecureRandom randomSource; - - public CryptorImpl(SecureRandom randomSource) { - this.randomSource = randomSource; - } - - @Override - public FilenameCryptor getFilenameCryptor() { - assertKeysExist(); - return LazyInitializer.initializeLazily(filenameCryptor, () -> { - return new FilenameCryptorImpl(encryptionKey, macKey); - }); - } - - @Override - public FileContentCryptor getFileContentCryptor() { - assertKeysExist(); - return LazyInitializer.initializeLazily(fileContentCryptor, () -> { - return new FileContentCryptorImpl(encryptionKey, macKey, randomSource); - }); - } - - private void assertKeysExist() { - if (encryptionKey == null || encryptionKey.isDestroyed()) { - throw new IllegalStateException("No or invalid encryptionKey."); - } - if (macKey == null || macKey.isDestroyed()) { - throw new IllegalStateException("No or invalid MAC key."); - } - } - - @Override - public void randomizeMasterkey() { - try { - KeyGenerator encKeyGen = KeyGenerator.getInstance(ENCRYPTION_ALG); - encKeyGen.init(KEYLENGTH_IN_BYTES * Byte.SIZE, randomSource); - encryptionKey = encKeyGen.generateKey(); - KeyGenerator macKeyGen = KeyGenerator.getInstance(MAC_ALG); - macKeyGen.init(KEYLENGTH_IN_BYTES * Byte.SIZE, randomSource); - macKey = macKeyGen.generateKey(); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("Hard-coded algorithm doesn't exist.", e); - } - } - - @Override - public void readKeysFromMasterkeyFile(byte[] masterkeyFileContents, CharSequence passphrase) { - final KeyFile keyFile; - try { - final ObjectMapper om = new ObjectMapper(); - keyFile = om.readValue(masterkeyFileContents, KeyFile.class); - if (keyFile == null) { - throw new InvalidFormatException("Could not read masterkey file", null, KeyFile.class); - } - } catch (IOException e) { - throw new IllegalArgumentException("Unable to parse masterkeyFileContents", e); - } - assert keyFile != null; - - // check version - if (!CURRENT_VAULT_VERSION.equals(keyFile.getVersion())) { - throw new UnsupportedVaultFormatException(keyFile.getVersion(), CURRENT_VAULT_VERSION); - } - - final byte[] kekBytes = Scrypt.scrypt(passphrase, keyFile.getScryptSalt(), keyFile.getScryptCostParam(), keyFile.getScryptBlockSize(), KEYLENGTH_IN_BYTES); - try { - final SecretKey kek = new SecretKeySpec(kekBytes, ENCRYPTION_ALG); - this.macKey = AesKeyWrap.unwrap(kek, keyFile.getMacMasterKey(), MAC_ALG); - // future use (as soon as we need to prevent downgrade attacks): - // final Mac mac = new ThreadLocalMac(macKey, MAC_ALG).get(); - // final byte[] versionMac = mac.doFinal(ByteBuffer.allocate(Integer.BYTES).putInt(CURRENT_VAULT_VERSION).array()); - // if (!MessageDigest.isEqual(versionMac, keyFile.getVersionMac())) { - // destroyQuietly(macKey); - // throw new UnsupportedVaultFormatException(Integer.MAX_VALUE, CURRENT_VAULT_VERSION); - // } - this.encryptionKey = AesKeyWrap.unwrap(kek, keyFile.getEncryptionMasterKey(), ENCRYPTION_ALG); - } catch (InvalidKeyException e) { - throw new InvalidPassphraseException(); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("Hard-coded algorithm doesn't exist.", e); - } finally { - Arrays.fill(kekBytes, (byte) 0x00); - } - } - - @Override - public byte[] writeKeysToMasterkeyFile(CharSequence passphrase) { - final byte[] scryptSalt = new byte[SCRYPT_SALT_LENGTH]; - randomSource.nextBytes(scryptSalt); - - final byte[] kekBytes = Scrypt.scrypt(passphrase, scryptSalt, SCRYPT_COST_PARAM, SCRYPT_BLOCK_SIZE, KEYLENGTH_IN_BYTES); - final byte[] wrappedEncryptionKey; - final byte[] wrappedMacKey; - try { - final SecretKey kek = new SecretKeySpec(kekBytes, ENCRYPTION_ALG); - wrappedEncryptionKey = AesKeyWrap.wrap(kek, encryptionKey); - wrappedMacKey = AesKeyWrap.wrap(kek, macKey); - } finally { - Arrays.fill(kekBytes, (byte) 0x00); - } - - final Mac mac = new ThreadLocalMac(macKey, MAC_ALG).get(); - final byte[] versionMac = mac.doFinal(ByteBuffer.allocate(Integer.BYTES).putInt(CURRENT_VAULT_VERSION).array()); - - final KeyFile keyfile = new KeyFile(); - keyfile.setVersion(CURRENT_VAULT_VERSION); - keyfile.setScryptSalt(scryptSalt); - keyfile.setScryptCostParam(SCRYPT_COST_PARAM); - keyfile.setScryptBlockSize(SCRYPT_BLOCK_SIZE); - keyfile.setEncryptionMasterKey(wrappedEncryptionKey); - keyfile.setMacMasterKey(wrappedMacKey); - keyfile.setVersionMac(versionMac); - - try { - final ObjectMapper om = new ObjectMapper(); - return om.writeValueAsBytes(keyfile); - } catch (JsonProcessingException e) { - throw new IllegalArgumentException("Unable to create JSON from " + keyfile, e); - } - } - - /* ======================= destruction ======================= */ - - @Override - public void destroy() { - destroyQuietly(encryptionKey); - destroyQuietly(macKey); - } - - @Override - public boolean isDestroyed() { - return (encryptionKey == null || encryptionKey.isDestroyed()) && (macKey == null || macKey.isDestroyed()); - } - - private void destroyQuietly(Destroyable d) { - if (d == null) { - return; - } - try { - d.destroy(); - } catch (DestroyFailedException e) { - // ignore - } - } - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FifoParallelDataProcessor.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FifoParallelDataProcessor.java deleted file mode 100644 index 811ca7556..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FifoParallelDataProcessor.java +++ /dev/null @@ -1,70 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto.engine.impl; - -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; -import java.util.concurrent.ThreadPoolExecutor; - -/** - * Executes long-running computations and returns the result strictly in order of the job submissions, no matter how long each job takes. - * - * The internally used thread pool is shut down automatically as soon as this FifiParallelDataProcessor is no longer referenced (see Finalization behaviour of {@link ThreadPoolExecutor}). - */ -class FifoParallelDataProcessor { - - private final BlockingQueue> processedData; - private final ExecutorService executorService; - - /** - * @param numThreads How many jobs can run in parallel. - * @param workAhead Maximum number of jobs accepted in {@link #submit(Callable)} without blocking until results are polled from {@link #processedData()}. - */ - public FifoParallelDataProcessor(int workAhead, ExecutorService executorService) { - this.processedData = new ArrayBlockingQueue<>(workAhead); - this.executorService = executorService; - } - - /** - * Enqueues a job for execution. The results of multiple submissions can be polled in FIFO order using {@link #processedData()}. - * - * @param processingJob A task, that will compute a result. - * @throws InterruptedException - */ - void submit(Callable processingJob) throws InterruptedException { - Future future = executorService.submit(processingJob); - processedData.put(future); - } - - /** - * Submits already pre-processed data, that can be polled in FIFO order from {@link #processedData()}. - * - * @throws InterruptedException - */ - void submitPreprocessed(T preprocessedData) throws InterruptedException { - this.submit(() -> { - return preprocessedData; - }); - } - - /** - * Result of previously {@link #submit(Callable) submitted} jobs in the same order as they have been submitted. Blocks if the job didn't finish yet. - * - * @return Next job result - * @throws InterruptedException If the calling thread was interrupted while waiting for the next result. - */ - T processedData() throws InterruptedException, ExecutionException { - return processedData.take().get(); - } - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentCryptorImpl.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentCryptorImpl.java deleted file mode 100644 index 4cb800cda..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentCryptorImpl.java +++ /dev/null @@ -1,75 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto.engine.impl; - -import static org.cryptomator.crypto.engine.impl.Constants.CHUNK_SIZE; -import static org.cryptomator.crypto.engine.impl.Constants.PAYLOAD_SIZE; - -import java.nio.ByteBuffer; -import java.security.SecureRandom; -import java.util.Optional; - -import javax.crypto.SecretKey; - -import org.cryptomator.crypto.engine.AuthenticationFailedException; -import org.cryptomator.crypto.engine.FileContentCryptor; -import org.cryptomator.crypto.engine.FileContentDecryptor; -import org.cryptomator.crypto.engine.FileContentEncryptor; - -class FileContentCryptorImpl implements FileContentCryptor { - - private final SecretKey encryptionKey; - private final SecretKey macKey; - private final SecureRandom randomSource; - - FileContentCryptorImpl(SecretKey encryptionKey, SecretKey macKey, SecureRandom randomSource) { - this.encryptionKey = encryptionKey; - this.macKey = macKey; - this.randomSource = randomSource; - } - - @Override - public int getHeaderSize() { - return FileHeader.HEADER_SIZE; - } - - @Override - public long toCiphertextPos(long cleartextPos) { - long chunkNum = cleartextPos / PAYLOAD_SIZE; - long cleartextChunkStart = chunkNum * PAYLOAD_SIZE; - assert cleartextChunkStart <= cleartextPos; - long chunkInternalDiff = cleartextPos - cleartextChunkStart; - assert chunkInternalDiff >= 0 && chunkInternalDiff < PAYLOAD_SIZE; - long ciphertextChunkStart = chunkNum * CHUNK_SIZE; - return ciphertextChunkStart + chunkInternalDiff; - } - - @Override - public FileContentDecryptor createFileContentDecryptor(ByteBuffer header, long firstCiphertextByte, boolean authenticate) throws IllegalArgumentException, AuthenticationFailedException { - if (header.remaining() != getHeaderSize()) { - throw new IllegalArgumentException("Invalid header."); - } - if (firstCiphertextByte % CHUNK_SIZE != 0) { - throw new IllegalArgumentException("Invalid starting point for decryption."); - } - return new FileContentDecryptorImpl(encryptionKey, macKey, header, firstCiphertextByte, authenticate); - } - - @Override - public FileContentEncryptor createFileContentEncryptor(Optional header, long firstCleartextByte) { - if (header.isPresent() && header.get().remaining() != getHeaderSize()) { - throw new IllegalArgumentException("Invalid header."); - } - if (firstCleartextByte % PAYLOAD_SIZE != 0) { - throw new IllegalArgumentException("Invalid starting point for encryption."); - } - return new FileContentEncryptorImpl(encryptionKey, macKey, randomSource, firstCleartextByte); - } - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImpl.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImpl.java deleted file mode 100644 index fdc12bb2a..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImpl.java +++ /dev/null @@ -1,180 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto.engine.impl; - -import static org.cryptomator.crypto.engine.impl.Constants.CHUNK_SIZE; -import static org.cryptomator.crypto.engine.impl.Constants.MAC_SIZE; -import static org.cryptomator.crypto.engine.impl.Constants.NONCE_SIZE; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.MessageDigest; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.function.Supplier; - -import javax.crypto.Cipher; -import javax.crypto.Mac; -import javax.crypto.SecretKey; -import javax.crypto.ShortBufferException; -import javax.crypto.spec.IvParameterSpec; - -import org.cryptomator.crypto.engine.AuthenticationFailedException; -import org.cryptomator.crypto.engine.FileContentCryptor; -import org.cryptomator.crypto.engine.FileContentDecryptor; -import org.cryptomator.io.ByteBuffers; - -class FileContentDecryptorImpl implements FileContentDecryptor { - - private static final String HMAC_SHA256 = "HmacSHA256"; - private static final int NUM_THREADS = Runtime.getRuntime().availableProcessors(); - private static final int READ_AHEAD = 2; - private static final ExecutorService SHARED_DECRYPTION_EXECUTOR = Executors.newFixedThreadPool(NUM_THREADS); - - private final FifoParallelDataProcessor dataProcessor = new FifoParallelDataProcessor<>(NUM_THREADS + READ_AHEAD, SHARED_DECRYPTION_EXECUTOR); - private final Supplier hmacSha256; - private final FileHeader header; - private final boolean authenticate; - private ByteBuffer ciphertextBuffer = ByteBuffer.allocate(CHUNK_SIZE); - private long chunkNumber = 0; - - public FileContentDecryptorImpl(SecretKey headerKey, SecretKey macKey, ByteBuffer header, long firstCiphertextByte, boolean authenticate) { - this.hmacSha256 = new ThreadLocalMac(macKey, HMAC_SHA256); - this.header = FileHeader.decrypt(headerKey, hmacSha256, header); - this.authenticate = authenticate; - this.chunkNumber = firstCiphertextByte / CHUNK_SIZE; // floor() by int-truncation - - // vault version 5 and onwards should have filesize: -1 - if (this.header.getPayload().getFilesize() != -1l) { - throw new UncheckedIOException(new IOException("Attempted to decrypt file with invalid header (probably from previous vault version)")); - } - } - - @Override - public void append(ByteBuffer ciphertext) throws InterruptedException { - if (ciphertext == FileContentCryptor.EOF) { - submitCiphertextBuffer(); - submitEof(); - } else { - while (ciphertext.hasRemaining()) { - ByteBuffers.copy(ciphertext, ciphertextBuffer); - submitCiphertextBufferIfFull(); - } - } - } - - @Override - public void cancelWithException(Exception cause) throws InterruptedException { - dataProcessor.submit(() -> { - throw cause; - }); - } - - private void submitCiphertextBufferIfFull() throws InterruptedException { - if (!ciphertextBuffer.hasRemaining()) { - submitCiphertextBuffer(); - ciphertextBuffer = ByteBuffer.allocate(CHUNK_SIZE); - } - } - - private void submitCiphertextBuffer() throws InterruptedException { - ciphertextBuffer.flip(); - if (ciphertextBuffer.hasRemaining()) { - Callable encryptionJob = new DecryptionJob(ciphertextBuffer, chunkNumber++); - dataProcessor.submit(encryptionJob); - } - } - - private void submitEof() throws InterruptedException { - dataProcessor.submitPreprocessed(FileContentCryptor.EOF); - } - - @Override - public ByteBuffer cleartext() throws InterruptedException { - try { - return dataProcessor.processedData(); - } catch (ExecutionException e) { - if (e.getCause() instanceof AuthenticationFailedException) { - throw new AuthenticationFailedException(e); - } else if (e.getCause() instanceof IOException || e.getCause() instanceof UncheckedIOException) { - throw new UncheckedIOException(new IOException("Decryption failed due to I/O exception during ciphertext supply.", e)); - } else { - throw new RuntimeException(e); - } - } - } - - @Override - public void destroy() { - header.destroy(); - } - - private class DecryptionJob implements Callable { - - private final byte[] nonce; - private final ByteBuffer inBuf; - private final ByteBuffer chunkNumberBigEndian = ByteBuffer.allocate(Long.BYTES); - private final byte[] expectedMac; - - public DecryptionJob(ByteBuffer ciphertextChunk, long chunkNumber) { - if (ciphertextChunk.remaining() < NONCE_SIZE + MAC_SIZE) { - throw new IllegalArgumentException("Chunk must at least contain a NONCE and a MAC"); - } - this.nonce = new byte[NONCE_SIZE]; - ByteBuffer nonceBuf = ciphertextChunk.asReadOnlyBuffer(); - nonceBuf.position(0).limit(NONCE_SIZE); - nonceBuf.get(nonce); - this.inBuf = ciphertextChunk.asReadOnlyBuffer(); - this.inBuf.position(NONCE_SIZE).limit(ciphertextChunk.limit() - MAC_SIZE); - chunkNumberBigEndian.putLong(chunkNumber); - chunkNumberBigEndian.rewind(); - this.expectedMac = new byte[MAC_SIZE]; - ByteBuffer macBuf = ciphertextChunk.asReadOnlyBuffer(); - macBuf.position(macBuf.limit() - MAC_SIZE); - macBuf.get(expectedMac); - } - - @Override - public ByteBuffer call() { - try { - if (authenticate) { - Mac mac = hmacSha256.get(); - mac.update(header.getIv()); - mac.update(chunkNumberBigEndian.asReadOnlyBuffer()); - mac.update(nonce); - mac.update(inBuf.asReadOnlyBuffer()); - if (!MessageDigest.isEqual(expectedMac, mac.doFinal())) { - chunkNumberBigEndian.rewind(); - throw new AuthenticationFailedException("Auth error in chunk " + chunkNumberBigEndian.getLong()); - } - } - - Cipher cipher = ThreadLocalAesCtrCipher.get(); - cipher.init(Cipher.DECRYPT_MODE, header.getPayload().getContentKey(), new IvParameterSpec(nonce)); - ByteBuffer outBuf = ByteBuffer.allocate(cipher.getOutputSize(inBuf.remaining())); - cipher.update(inBuf, outBuf); - outBuf.flip(); - return outBuf; - } catch (InvalidKeyException e) { - throw new IllegalStateException("File content key created by current class invalid.", e); - } catch (ShortBufferException e) { - throw new IllegalStateException("Buffer allocated for reported output size apparently not big enought.", e); - } catch (InvalidAlgorithmParameterException e) { - throw new IllegalStateException("CTR mode known to accept an IV (aka. nonce).", e); - } - } - - } - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImpl.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImpl.java deleted file mode 100644 index 71c0b121b..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImpl.java +++ /dev/null @@ -1,187 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto.engine.impl; - -import static org.cryptomator.crypto.engine.impl.Constants.NONCE_SIZE; -import static org.cryptomator.crypto.engine.impl.Constants.PAYLOAD_SIZE; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.SecureRandom; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.LongAdder; - -import javax.crypto.Cipher; -import javax.crypto.Mac; -import javax.crypto.SecretKey; -import javax.crypto.ShortBufferException; -import javax.crypto.spec.IvParameterSpec; - -import org.cryptomator.crypto.engine.FileContentCryptor; -import org.cryptomator.crypto.engine.FileContentEncryptor; -import org.cryptomator.io.ByteBuffers; - -class FileContentEncryptorImpl implements FileContentEncryptor { - - private static final String HMAC_SHA256 = "HmacSHA256"; - private static final int NUM_THREADS = Runtime.getRuntime().availableProcessors(); - private static final int READ_AHEAD = 2; - private static final ExecutorService SHARED_DECRYPTION_EXECUTOR = Executors.newFixedThreadPool(NUM_THREADS); - - private final FifoParallelDataProcessor dataProcessor = new FifoParallelDataProcessor<>(NUM_THREADS + READ_AHEAD, SHARED_DECRYPTION_EXECUTOR); - private final ThreadLocalMac hmacSha256; - private final SecretKey headerKey; - private final FileHeader header; - private final SecureRandom randomSource; - private final LongAdder cleartextBytesScheduledForEncryption = new LongAdder(); - private ByteBuffer cleartextBuffer = ByteBuffer.allocate(PAYLOAD_SIZE); - private long chunkNumber = 0; - - public FileContentEncryptorImpl(SecretKey headerKey, SecretKey macKey, SecureRandom randomSource, long firstCleartextByte) { - if (firstCleartextByte != 0) { - throw new UnsupportedOperationException("Partial encryption not supported."); - } - this.hmacSha256 = new ThreadLocalMac(macKey, HMAC_SHA256); - this.headerKey = headerKey; - this.header = new FileHeader(randomSource); - this.randomSource = randomSource; - } - - @Override - public ByteBuffer getHeader() { - header.getPayload().setFilesize(-1l); - return header.toByteBuffer(headerKey, hmacSha256); - } - - @Override - public int getHeaderSize() { - return FileHeader.HEADER_SIZE; - } - - @Override - public void append(ByteBuffer cleartext) throws InterruptedException { - cleartextBytesScheduledForEncryption.add(cleartext.remaining()); - if (cleartext == FileContentCryptor.EOF) { - submitCleartextBuffer(); - submitEof(); - } else { - appendAllAndSubmitIfFull(cleartext); - } - } - - private void appendAllAndSubmitIfFull(ByteBuffer cleartext) throws InterruptedException { - while (cleartext.hasRemaining()) { - ByteBuffers.copy(cleartext, cleartextBuffer); - submitCleartextBufferIfFull(); - } - } - - @Override - public void cancelWithException(Exception cause) throws InterruptedException { - dataProcessor.submit(() -> { - throw cause; - }); - } - - private void submitCleartextBufferIfFull() throws InterruptedException { - if (!cleartextBuffer.hasRemaining()) { - submitCleartextBuffer(); - cleartextBuffer = ByteBuffer.allocate(PAYLOAD_SIZE); - } - } - - private void submitCleartextBuffer() throws InterruptedException { - cleartextBuffer.flip(); - if (cleartextBuffer.hasRemaining()) { - Callable encryptionJob = new EncryptionJob(cleartextBuffer, chunkNumber++); - dataProcessor.submit(encryptionJob); - } - } - - private void submitEof() throws InterruptedException { - dataProcessor.submitPreprocessed(FileContentCryptor.EOF); - } - - @Override - public ByteBuffer ciphertext() throws InterruptedException { - try { - return dataProcessor.processedData(); - } catch (ExecutionException e) { - if (e.getCause() instanceof IOException || e.getCause() instanceof UncheckedIOException) { - throw new UncheckedIOException(new IOException("Encryption failed due to I/O exception during cleartext supply.", e)); - } else { - throw new RuntimeException(e); - } - } - } - - @Override - public void destroy() { - header.destroy(); - } - - private class EncryptionJob implements Callable { - - private final ByteBuffer inBuf; - private final ByteBuffer chunkNumberBigEndian = ByteBuffer.allocate(Long.BYTES); - - public EncryptionJob(ByteBuffer cleartextChunk, long chunkNumber) { - this.inBuf = cleartextChunk; - chunkNumberBigEndian.putLong(chunkNumber); - chunkNumberBigEndian.rewind(); - } - - @Override - public ByteBuffer call() { - try { - final Cipher cipher = ThreadLocalAesCtrCipher.get(); - final Mac mac = hmacSha256.get(); - final ByteBuffer outBuf = ByteBuffer.allocate(NONCE_SIZE + inBuf.remaining() + mac.getMacLength()); - - // nonce - byte[] nonce = new byte[NONCE_SIZE]; - randomSource.nextBytes(nonce); - outBuf.put(nonce); - - // payload: - cipher.init(Cipher.ENCRYPT_MODE, header.getPayload().getContentKey(), new IvParameterSpec(nonce)); - assert cipher.getOutputSize(inBuf.remaining()) == inBuf.remaining() : "input length should be equal to output length in CTR mode."; - int bytesEncrypted = cipher.update(inBuf, outBuf); - - // mac: - ByteBuffer ciphertextBuf = outBuf.asReadOnlyBuffer(); - ciphertextBuf.position(NONCE_SIZE).limit(NONCE_SIZE + bytesEncrypted); - mac.update(header.getIv()); - mac.update(chunkNumberBigEndian.asReadOnlyBuffer()); - mac.update(nonce); - mac.update(ciphertextBuf); - byte[] authenticationCode = mac.doFinal(); - outBuf.put(authenticationCode); - - // flip and return: - outBuf.flip(); - return outBuf; - } catch (InvalidKeyException e) { - throw new IllegalStateException("File content key created by current class invalid.", e); - } catch (ShortBufferException e) { - throw new IllegalStateException("Buffer allocated for reported output size apparently not big enought.", e); - } catch (InvalidAlgorithmParameterException e) { - throw new IllegalStateException("CTR mode known to accept an IV (aka. nonce).", e); - } - } - - } - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileHeader.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileHeader.java deleted file mode 100644 index 18accc34b..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileHeader.java +++ /dev/null @@ -1,117 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 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.crypto.engine.impl; - -import java.nio.ByteBuffer; -import java.security.MessageDigest; -import java.security.SecureRandom; -import java.util.function.Supplier; - -import javax.crypto.Mac; -import javax.crypto.SecretKey; -import javax.security.auth.Destroyable; - -import org.cryptomator.crypto.engine.AuthenticationFailedException; - -class FileHeader implements Destroyable { - - static final int HEADER_SIZE = 88; - - private static final int IV_POS = 0; - private static final int IV_LEN = 16; - private static final int PAYLOAD_POS = 16; - private static final int PAYLOAD_LEN = 40; - private static final int MAC_POS = 56; - private static final int MAC_LEN = 32; - - private final byte[] iv; - private final FileHeaderPayload payload; - - public FileHeader(SecureRandom randomSource) { - this.iv = new byte[IV_LEN]; - this.payload = new FileHeaderPayload(randomSource); - randomSource.nextBytes(iv); - } - - private FileHeader(byte[] iv, FileHeaderPayload payload) { - this.iv = iv; - this.payload = payload; - } - - public byte[] getIv() { - return iv; - } - - public FileHeaderPayload getPayload() { - return payload; - } - - public ByteBuffer toByteBuffer(SecretKey headerKey, Supplier hmacSha256Factory) { - ByteBuffer result = ByteBuffer.allocate(HEADER_SIZE); - result.position(IV_POS).limit(IV_POS + IV_LEN); - result.put(iv); - result.position(PAYLOAD_POS).limit(PAYLOAD_POS + PAYLOAD_LEN); - result.put(payload.toCiphertextByteBuffer(headerKey, iv)); - ByteBuffer resultSoFar = result.asReadOnlyBuffer(); - resultSoFar.flip(); - Mac mac = hmacSha256Factory.get(); - assert mac.getMacLength() == MAC_LEN; - mac.update(resultSoFar); - result.position(MAC_POS).limit(MAC_POS + MAC_LEN); - result.put(mac.doFinal()); - result.flip(); - return result; - } - - @Override - public boolean isDestroyed() { - return payload.isDestroyed(); - } - - @Override - public void destroy() { - payload.destroy(); - } - - public static FileHeader decrypt(SecretKey headerKey, Supplier hmacSha256Factory, ByteBuffer header) throws IllegalArgumentException, AuthenticationFailedException { - if (header.remaining() != HEADER_SIZE) { - throw new IllegalArgumentException("Invalid header size."); - } - - checkHeaderMac(header, hmacSha256Factory.get()); - - final byte[] iv = new byte[IV_LEN]; - final ByteBuffer ivBuf = header.asReadOnlyBuffer(); - ivBuf.position(IV_POS).limit(IV_POS + IV_LEN); - ivBuf.get(iv); - - final ByteBuffer payloadBuf = header.asReadOnlyBuffer(); - payloadBuf.position(PAYLOAD_POS).limit(PAYLOAD_POS + PAYLOAD_LEN); - - final FileHeaderPayload payload = FileHeaderPayload.fromCiphertextByteBuffer(payloadBuf, headerKey, iv); - - return new FileHeader(iv, payload); - } - - private static void checkHeaderMac(ByteBuffer header, Mac mac) throws AuthenticationFailedException { - assert mac.getMacLength() == MAC_LEN; - ByteBuffer headerData = header.asReadOnlyBuffer(); - headerData.position(0).limit(MAC_POS); - mac.update(headerData); - ByteBuffer headerMac = header.asReadOnlyBuffer(); - headerMac.position(MAC_POS).limit(MAC_POS + MAC_LEN); - byte[] expectedMac = new byte[MAC_LEN]; - headerMac.get(expectedMac); - - if (!MessageDigest.isEqual(expectedMac, mac.doFinal())) { - throw new AuthenticationFailedException("Corrupt header."); - } - } - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileHeaderPayload.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileHeaderPayload.java deleted file mode 100644 index 2016dbfa1..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileHeaderPayload.java +++ /dev/null @@ -1,149 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 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.crypto.engine.impl; - -import java.nio.ByteBuffer; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.Arrays; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; -import javax.crypto.ShortBufferException; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; -import javax.security.auth.DestroyFailedException; -import javax.security.auth.Destroyable; - -class FileHeaderPayload implements Destroyable { - - private static final int FILESIZE_POS = 0; - private static final int FILESIZE_LEN = Long.BYTES; - private static final int CONTENT_KEY_POS = 8; - private static final int CONTENT_KEY_LEN = 32; - private static final String AES = "AES"; - - private long filesize; - private final SecretKey contentKey; - - public FileHeaderPayload(SecureRandom randomSource) { - this.filesize = 0; - try { - KeyGenerator keyGen = KeyGenerator.getInstance(AES); - keyGen.init(CONTENT_KEY_LEN * Byte.SIZE, randomSource); - this.contentKey = keyGen.generateKey(); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("Hard-coded algorithm doesn't exist.", e); - } - } - - private FileHeaderPayload(long filesize, SecretKey contentKey) { - this.filesize = filesize; - this.contentKey = contentKey; - } - - public long getFilesize() { - return filesize; - } - - public void setFilesize(long filesize) { - this.filesize = filesize; - } - - public SecretKey getContentKey() { - return contentKey; - } - - @Override - public boolean isDestroyed() { - return contentKey.isDestroyed(); - } - - @Override - public void destroy() { - try { - contentKey.destroy(); - } catch (DestroyFailedException e) { - // no-op - } - } - - private ByteBuffer toCleartextByteBuffer() { - ByteBuffer cleartext = ByteBuffer.allocate(FILESIZE_LEN + CONTENT_KEY_LEN); - cleartext.position(FILESIZE_POS).limit(FILESIZE_POS + FILESIZE_LEN); - cleartext.putLong(filesize); - cleartext.position(CONTENT_KEY_POS).limit(CONTENT_KEY_POS + CONTENT_KEY_LEN); - cleartext.put(contentKey.getEncoded()); - cleartext.flip(); - return cleartext; - } - - public ByteBuffer toCiphertextByteBuffer(SecretKey headerKey, byte[] iv) { - final ByteBuffer cleartext = toCleartextByteBuffer(); - try { - Cipher cipher = ThreadLocalAesCtrCipher.get(); - cipher.init(Cipher.ENCRYPT_MODE, headerKey, new IvParameterSpec(iv)); - final int ciphertextLength = cipher.getOutputSize(cleartext.remaining()); - assert ciphertextLength == cleartext.remaining() : "in counter mode outputlength == input length"; - final ByteBuffer ciphertext = ByteBuffer.allocate(ciphertextLength); - cipher.doFinal(cleartext, ciphertext); - ciphertext.flip(); - return ciphertext; - } catch (InvalidKeyException | InvalidAlgorithmParameterException | ShortBufferException | IllegalBlockSizeException | BadPaddingException e) { - throw new IllegalStateException("Unable to compute encrypted header.", e); - } finally { - Arrays.fill(cleartext.array(), (byte) 0x00); - } - } - - public static FileHeaderPayload fromCiphertextByteBuffer(ByteBuffer ciphertextPayload, SecretKey headerKey, byte[] iv) { - final ByteBuffer cleartext = decryptPayload(ciphertextPayload, headerKey, iv); - try { - return fromCleartextByteBuffer(cleartext); - } finally { - // destroy evidence: - Arrays.fill(cleartext.array(), (byte) 0x00); - } - } - - private static FileHeaderPayload fromCleartextByteBuffer(ByteBuffer cleartext) { - final byte[] contentKey = new byte[CONTENT_KEY_LEN]; - try { - cleartext.position(FILESIZE_POS).limit(FILESIZE_POS + FILESIZE_LEN); - final long filesize = cleartext.getLong(); - cleartext.position(CONTENT_KEY_POS).limit(CONTENT_KEY_POS + CONTENT_KEY_LEN); - cleartext.get(contentKey); - return new FileHeaderPayload(filesize, new SecretKeySpec(contentKey, AES)); - } finally { - // destroy evidence: - Arrays.fill(contentKey, (byte) 0x00); - } - } - - private static ByteBuffer decryptPayload(ByteBuffer ciphertext, SecretKey headerKey, byte[] iv) { - try { - Cipher cipher = ThreadLocalAesCtrCipher.get(); - cipher.init(Cipher.DECRYPT_MODE, headerKey, new IvParameterSpec(iv)); - final int cleartextLength = cipher.getOutputSize(ciphertext.remaining()); - assert cleartextLength == ciphertext.remaining() : "in counter mode outputlength == input length"; - final ByteBuffer cleartext = ByteBuffer.allocate(cleartextLength); - cipher.doFinal(ciphertext, cleartext); - cleartext.flip(); - return cleartext; - } catch (InvalidKeyException | InvalidAlgorithmParameterException | ShortBufferException | IllegalBlockSizeException | BadPaddingException e) { - throw new IllegalStateException("Unable to decrypt header.", e); - } - } - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImpl.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImpl.java deleted file mode 100644 index 2066fe5f4..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImpl.java +++ /dev/null @@ -1,98 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto.engine.impl; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.regex.Pattern; - -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.SecretKey; - -import org.apache.commons.codec.binary.Base32; -import org.apache.commons.codec.binary.BaseNCodec; -import org.cryptomator.crypto.engine.AuthenticationFailedException; -import org.cryptomator.crypto.engine.FilenameCryptor; -import org.cryptomator.siv.SivMode; -import org.cryptomator.siv.UnauthenticCiphertextException; - -class FilenameCryptorImpl implements FilenameCryptor { - - private static final BaseNCodec BASE32 = new Base32(); - // https://tools.ietf.org/html/rfc4648#section-6 - private static final Pattern BASE32_PATTERN = Pattern.compile("^([A-Z2-7]{8})*[A-Z2-7=]{8}"); - private static final ThreadLocal SHA1 = new ThreadLocalSha1(); - private static final ThreadLocal AES_SIV = new ThreadLocal() { - @Override - protected SivMode initialValue() { - return new SivMode(); - }; - }; - - private final SecretKey encryptionKey; - private final SecretKey macKey; - - FilenameCryptorImpl(SecretKey encryptionKey, SecretKey macKey) { - this.encryptionKey = encryptionKey; - this.macKey = macKey; - } - - @Override - public String hashDirectoryId(String cleartextDirectoryId) { - final byte[] cleartextBytes = cleartextDirectoryId.getBytes(UTF_8); - byte[] encryptedBytes = AES_SIV.get().encrypt(encryptionKey, macKey, cleartextBytes); - final byte[] hashedBytes = SHA1.get().digest(encryptedBytes); - return BASE32.encodeAsString(hashedBytes); - } - - @Override - public Pattern encryptedNamePattern() { - return BASE32_PATTERN; - } - - @Override - public String encryptFilename(String cleartextName, byte[]... associatedData) { - final byte[] cleartextBytes = cleartextName.getBytes(UTF_8); - final byte[] encryptedBytes = AES_SIV.get().encrypt(encryptionKey, macKey, cleartextBytes, associatedData); - return BASE32.encodeAsString(encryptedBytes); - } - - @Override - public String decryptFilename(String ciphertextName, byte[]... associatedData) throws AuthenticationFailedException { - final byte[] encryptedBytes = BASE32.decode(ciphertextName); - try { - final byte[] cleartextBytes = AES_SIV.get().decrypt(encryptionKey, macKey, encryptedBytes, associatedData); - return new String(cleartextBytes, UTF_8); - } catch (UnauthenticCiphertextException | IllegalBlockSizeException e) { - throw new AuthenticationFailedException("Invalid ciphertext.", e); - } - } - - private static class ThreadLocalSha1 extends ThreadLocal { - - @Override - protected MessageDigest initialValue() { - try { - return MessageDigest.getInstance("SHA-1"); - } catch (NoSuchAlgorithmException e) { - throw new AssertionError("SHA-1 exists in every JVM"); - } - } - - @Override - public MessageDigest get() { - final MessageDigest messageDigest = super.get(); - messageDigest.reset(); - return messageDigest; - } - } - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/KeyFile.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/KeyFile.java deleted file mode 100644 index ee833c215..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/KeyFile.java +++ /dev/null @@ -1,100 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 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.crypto.engine.impl; - -import java.io.Serializable; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - -@JsonIgnoreProperties(ignoreUnknown = true) -@JsonPropertyOrder(value = {"version", "scryptSalt", "scryptCostParam", "scryptBlockSize", "primaryMasterKey", "hmacMasterKey", "versionMac"}) -class KeyFile implements Serializable { - - private static final long serialVersionUID = 8578363158959619885L; - - @JsonProperty("version") - private Integer version; - - @JsonProperty("scryptSalt") - private byte[] scryptSalt; - - @JsonProperty("scryptCostParam") - private int scryptCostParam; - - @JsonProperty("scryptBlockSize") - private int scryptBlockSize; - - @JsonProperty("primaryMasterKey") - private byte[] encryptionMasterKey; - - @JsonProperty("hmacMasterKey") - private byte[] macMasterKey; - - @JsonProperty("versionMac") - private byte[] versionMac; - - public Integer getVersion() { - return version; - } - - public void setVersion(Integer version) { - this.version = version; - } - - public byte[] getScryptSalt() { - return scryptSalt; - } - - public void setScryptSalt(byte[] scryptSalt) { - this.scryptSalt = scryptSalt; - } - - public int getScryptCostParam() { - return scryptCostParam; - } - - public void setScryptCostParam(int scryptCostParam) { - this.scryptCostParam = scryptCostParam; - } - - public int getScryptBlockSize() { - return scryptBlockSize; - } - - public void setScryptBlockSize(int scryptBlockSize) { - this.scryptBlockSize = scryptBlockSize; - } - - public byte[] getEncryptionMasterKey() { - return encryptionMasterKey; - } - - public void setEncryptionMasterKey(byte[] encryptionMasterKey) { - this.encryptionMasterKey = encryptionMasterKey; - } - - public byte[] getMacMasterKey() { - return macMasterKey; - } - - public void setMacMasterKey(byte[] macMasterKey) { - this.macMasterKey = macMasterKey; - } - - public byte[] getVersionMac() { - return versionMac; - } - - public void setVersionMac(byte[] versionMac) { - this.versionMac = versionMac; - } - -} \ No newline at end of file diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Scrypt.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Scrypt.java deleted file mode 100644 index 833f27a2f..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Scrypt.java +++ /dev/null @@ -1,50 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto.engine.impl; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.util.Arrays; - -import org.bouncycastle.crypto.generators.SCrypt; - -final class Scrypt { - - private Scrypt() { - } - - /** - * Derives a key from the given passphrase. - * This implementation makes sure, any copies of the passphrase used during key derivation are overwritten in memory asap (before next GC cycle). - * - * @param passphrase The passphrase - * @param salt Salt, ideally randomly generated - * @param costParam Cost parameter N, larger than 1, a power of 2 and less than 2^(128 * costParam / 8) - * @param blockSize Block size r - * @param keyLengthInBytes Key output length dkLen - * @return Derived key - * @see RFC Draft - */ - public static byte[] scrypt(CharSequence passphrase, byte[] salt, int costParam, int blockSize, int keyLengthInBytes) { - // This is an attempt to get the password bytes without copies of the password being created in some dark places inside the JVM: - final ByteBuffer buf = UTF_8.encode(CharBuffer.wrap(passphrase)); - final byte[] pw = new byte[buf.remaining()]; - buf.get(pw); - try { - return SCrypt.generate(pw, salt, costParam, blockSize, 1, keyLengthInBytes); - } finally { - Arrays.fill(pw, (byte) 0); // overwrite bytes - buf.rewind(); // just resets markers - buf.put(pw); // this is where we overwrite the actual bytes - } - } - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/ThreadLocalAesCtrCipher.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/ThreadLocalAesCtrCipher.java deleted file mode 100644 index f54676282..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/ThreadLocalAesCtrCipher.java +++ /dev/null @@ -1,36 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 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.crypto.engine.impl; - -import java.security.NoSuchAlgorithmException; - -import javax.crypto.Cipher; -import javax.crypto.NoSuchPaddingException; - -final class ThreadLocalAesCtrCipher { - - private ThreadLocalAesCtrCipher() { - } - - private static final String AES_CTR = "AES/CTR/NoPadding"; - private static final ThreadLocal THREAD_LOCAL_CIPHER = ThreadLocal.withInitial(ThreadLocalAesCtrCipher::newCipherInstance); - - private static Cipher newCipherInstance() { - try { - return Cipher.getInstance(AES_CTR); - } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { - throw new IllegalStateException("Could not create Cipher.", e); - } - } - - public static Cipher get() { - return THREAD_LOCAL_CIPHER.get(); - } - -} \ No newline at end of file diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/ThreadLocalMac.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/ThreadLocalMac.java deleted file mode 100644 index 68651768f..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/ThreadLocalMac.java +++ /dev/null @@ -1,46 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 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.crypto.engine.impl; - -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.util.function.Supplier; - -import javax.crypto.Mac; -import javax.crypto.SecretKey; - -class ThreadLocalMac extends ThreadLocalimplements Supplier { - - private final SecretKey macKey; - private final String macAlgorithm; - - ThreadLocalMac(SecretKey macKey, String macAlgorithm) { - this.macKey = macKey; - this.macAlgorithm = macAlgorithm; - } - - @Override - protected Mac initialValue() { - try { - Mac mac = Mac.getInstance(macAlgorithm); - mac.init(macKey); - return mac; - } catch (NoSuchAlgorithmException | InvalidKeyException e) { - throw new IllegalStateException("Could not create MAC.", e); - } - } - - @Override - public Mac get() { - Mac mac = super.get(); - mac.reset(); - return mac; - } - -} \ No newline at end of file diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/package-info.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/package-info.java deleted file mode 100644 index ed4065919..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/package-info.java +++ /dev/null @@ -1,12 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 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 - *******************************************************************************/ -/** - * This is where the actual encryption, decryption, hashing and authenticating takes place. - */ -package org.cryptomator.crypto.engine; \ No newline at end of file diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedFile.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedFile.java deleted file mode 100644 index ebd716a90..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedFile.java +++ /dev/null @@ -1,35 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto; - -import java.io.UncheckedIOException; - -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.delegating.DelegatingFile; - -class BlockAlignedFile extends DelegatingFile { - - private final int blockSize; - - public BlockAlignedFile(BlockAlignedFolder parent, File delegate, int blockSize) { - super(parent, delegate); - this.blockSize = blockSize; - } - - @Override - public BlockAlignedReadableFile openReadable() throws UncheckedIOException { - return new BlockAlignedReadableFile(delegate.openReadable(), blockSize); - } - - @Override - public BlockAlignedWritableFile openWritable() throws UncheckedIOException { - return new BlockAlignedWritableFile(delegate::openWritable, delegate::openReadable, blockSize); - } - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedFileSystem.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedFileSystem.java deleted file mode 100644 index b2a68ff54..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedFileSystem.java +++ /dev/null @@ -1,61 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto; - -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.delegating.DelegatingFileSystem; - -/** - * Provides a decoration layer for the {@link org.cryptomator.filesystem Filesystem API}, which guarantees, that all read/write attempts to underlying files always begin at a block start position. - * Block start positions are integer multiples of a block size + a fixed block shift. - *

- * In general the formula to align a requested read with a physical read is floor(x / blockSize) * blockSize
- * For example blockSize=10 result in the following block-aligned read/write attempts: - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Requested ReadPhysical Read
00
50
90
1010
1110
3530
- */ -class BlockAlignedFileSystem extends BlockAlignedFolder implements DelegatingFileSystem { - - public BlockAlignedFileSystem(Folder delegate, int blockSize) { - super(null, delegate, blockSize); - } - - @Override - public Folder getDelegate() { - return delegate; - } - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedFileSystemFactory.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedFileSystemFactory.java deleted file mode 100644 index f98efb479..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedFileSystemFactory.java +++ /dev/null @@ -1,29 +0,0 @@ -/******************************************************************************* - * 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.crypto; - -import static org.cryptomator.crypto.engine.impl.Constants.PAYLOAD_SIZE; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.Folder; - -@Singleton -public class BlockAlignedFileSystemFactory { - - @Inject - public BlockAlignedFileSystemFactory() { - } - - public FileSystem get(Folder root) { - return new BlockAlignedFileSystem(root, PAYLOAD_SIZE); - } -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedFolder.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedFolder.java deleted file mode 100644 index 5a930ad6a..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedFolder.java +++ /dev/null @@ -1,34 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto; - -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.delegating.DelegatingFolder; - -class BlockAlignedFolder extends DelegatingFolder { - - private final int blockSize; - - public BlockAlignedFolder(BlockAlignedFolder parent, Folder delegate, int blockSize) { - super(parent, delegate); - this.blockSize = blockSize; - } - - @Override - protected BlockAlignedFile newFile(File delegate) { - return new BlockAlignedFile(this, delegate, blockSize); - } - - @Override - protected BlockAlignedFolder newFolder(Folder delegate) { - return new BlockAlignedFolder(this, delegate, blockSize); - } - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedReadableFile.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedReadableFile.java deleted file mode 100644 index 4ae1c0a10..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedReadableFile.java +++ /dev/null @@ -1,108 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto; - -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; - -import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.io.ByteBuffers; - -class BlockAlignedReadableFile implements ReadableFile { - - private final ReadableFile delegate; - private final int blockSize; - private final ByteBuffer currentBlockBuffer; - private boolean eofReached = false; - private Mode mode = Mode.PASSTHROUGH; - - private enum Mode { - BLOCK_ALIGNED, PASSTHROUGH; - } - - public BlockAlignedReadableFile(ReadableFile delegate, int blockSize) { - if (blockSize < 1) { - throw new IllegalArgumentException("Invalid block size"); - } - this.delegate = delegate; - this.blockSize = blockSize; - this.currentBlockBuffer = ByteBuffer.allocate(blockSize); - this.currentBlockBuffer.flip(); // so remaining() is 0 -> next read will read from physical source. - } - - @Override - public void position(long logicalPosition) throws UncheckedIOException { - switchToBlockAlignedMode(); - long blockNumber = logicalPosition / blockSize; - long physicalPosition = blockNumber * blockSize; - assert physicalPosition <= logicalPosition; - int diff = (int) (logicalPosition - physicalPosition); - assert diff >= 0; - assert diff < blockSize; - delegate.position(physicalPosition); - eofReached = false; - readCurrentBlock(); - currentBlockBuffer.position(diff); - } - - // visible for testing - void switchToBlockAlignedMode() { - mode = Mode.BLOCK_ALIGNED; - } - - @Override - public int read(ByteBuffer target) throws UncheckedIOException { - switch (mode) { - case PASSTHROUGH: - return delegate.read(target); - case BLOCK_ALIGNED: - return readBlockAligned(target); - default: - throw new IllegalStateException("Unsupported mode " + mode); - } - } - - private int readBlockAligned(ByteBuffer target) { - if (eofReached) { - return -1; - } else { - int read = 0; - while (!eofReached && target.hasRemaining()) { - read += ByteBuffers.copy(currentBlockBuffer, target); - readCurrentBlockIfNeeded(); - } - return read; - } - } - - private void readCurrentBlockIfNeeded() { - if (!currentBlockBuffer.hasRemaining()) { - readCurrentBlock(); - } - } - - private void readCurrentBlock() { - currentBlockBuffer.clear(); - if (delegate.read(currentBlockBuffer) == -1) { - eofReached = true; - } - currentBlockBuffer.flip(); - } - - @Override - public boolean isOpen() { - return delegate.isOpen(); - } - - @Override - public void close() throws UncheckedIOException { - delegate.close(); - } - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedWritableFile.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedWritableFile.java deleted file mode 100644 index 10d19374e..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/BlockAlignedWritableFile.java +++ /dev/null @@ -1,136 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto; - -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.util.Optional; -import java.util.function.Supplier; - -import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.filesystem.WritableFile; -import org.cryptomator.io.ByteBuffers; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -class BlockAlignedWritableFile implements WritableFile { - - private static final Logger LOG = LoggerFactory.getLogger(BlockAlignedWritableFile.class); - - private final Supplier openWritable; - private final Supplier openReadable; - private final int blockSize; - private final ByteBuffer currentBlockBuffer; - private Mode mode = Mode.PASSTHROUGH; - private Optional delegate; - private long logicalPosition; - - private enum Mode { - BLOCK_ALIGNED, PASSTHROUGH; - } - - public BlockAlignedWritableFile(Supplier openWritable, Supplier openReadable, int blockSize) { - this.openWritable = openWritable; - this.openReadable = openReadable; - this.blockSize = blockSize; - this.currentBlockBuffer = ByteBuffer.allocate(blockSize); - currentBlockBuffer.flip(); // make sure the buffer has no remaining bytes by default - delegate = Optional.of(openWritable.get()); - } - - @Override - public void position(long logicalPosition) throws UncheckedIOException { - switchToBlockAlignedMode(); - this.logicalPosition = logicalPosition; - readCurrentBlock(); - } - - // visible for testing - void switchToBlockAlignedMode() { - LOG.trace("switching to blockaligend write..."); - mode = Mode.BLOCK_ALIGNED; - } - - @Override - public int write(ByteBuffer source) throws UncheckedIOException { - switch (mode) { - case PASSTHROUGH: - return delegate.get().write(source); - case BLOCK_ALIGNED: - return writeBlockAligned(source); - default: - throw new IllegalStateException("Unsupported mode " + mode); - } - } - - private int writeBlockAligned(ByteBuffer source) { - int writtenTotal = 0; - while (source.hasRemaining()) { - int written = ByteBuffers.copy(source, currentBlockBuffer); - logicalPosition += written; - writeCurrentBlockIfNeeded(); - writtenTotal += written; - } - return writtenTotal; - } - - @Override - public void close() throws UncheckedIOException { - currentBlockBuffer.flip(); - writeCurrentBlock(); - delegate.ifPresent(WritableFile::close); - } - - private void writeCurrentBlockIfNeeded() { - if (!currentBlockBuffer.hasRemaining()) { - writeCurrentBlock(); - readCurrentBlock(); - } - } - - private void writeCurrentBlock() { - currentBlockBuffer.rewind(); - delegate.get().write(currentBlockBuffer); - } - - private void readCurrentBlock() { - // TODO lock that shit - - // determine right position: - long blockNumber = logicalPosition / blockSize; - long physicalPosition = blockNumber * blockSize; - - // switch from write to read access: - delegate.get().close(); - currentBlockBuffer.clear(); - try (ReadableFile r = openReadable.get()) { - r.position(physicalPosition); - int numRead = r.read(currentBlockBuffer); - assert numRead == currentBlockBuffer.position(); - } - int advance = (int) (logicalPosition - physicalPosition); - currentBlockBuffer.position(advance); - - // continue write access: - WritableFile w = openWritable.get(); - w.position(physicalPosition); - delegate = Optional.of(w); - } - - @Override - public boolean isOpen() { - return delegate.get().isOpen(); - } - - @Override - public void truncate() throws UncheckedIOException { - delegate.get().truncate(); - } - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CiphertextReader.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CiphertextReader.java deleted file mode 100644 index 0f31ac6b8..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CiphertextReader.java +++ /dev/null @@ -1,63 +0,0 @@ -/******************************************************************************* - * 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.crypto; - -import java.io.InterruptedIOException; -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.util.concurrent.Callable; - -import org.cryptomator.crypto.engine.FileContentCryptor; -import org.cryptomator.crypto.engine.FileContentDecryptor; -import org.cryptomator.filesystem.ReadableFile; - -class CiphertextReader implements Callable { - - private static final int READ_BUFFER_SIZE = 32 * 1024 + 32; // aligned with encrypted chunk size + MAC size - - private final ReadableFile file; - private final FileContentDecryptor decryptor; - private final long startpos; - - public CiphertextReader(ReadableFile file, FileContentDecryptor decryptor, long startpos) { - this.file = file; - this.decryptor = decryptor; - this.startpos = startpos; - } - - @Override - public Void call() throws InterruptedIOException { - try { - callInterruptibly(); - } catch (InterruptedException e) { - throw new InterruptedIOException("Task interrupted while waiting for ciphertext"); - } - return null; - } - - private void callInterruptibly() throws InterruptedException { - try { - file.position(startpos); - int bytesRead = -1; - do { - ByteBuffer ciphertext = ByteBuffer.allocate(READ_BUFFER_SIZE); - bytesRead = file.read(ciphertext); - if (bytesRead > 0) { - ciphertext.flip(); - assert bytesRead == ciphertext.remaining(); - decryptor.append(ciphertext); - } - } while (bytesRead > 0); - decryptor.append(FileContentCryptor.EOF); - } catch (UncheckedIOException e) { - decryptor.cancelWithException(e); - } - } - -} \ No newline at end of file diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CiphertextWriter.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CiphertextWriter.java deleted file mode 100644 index f325c2acf..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CiphertextWriter.java +++ /dev/null @@ -1,51 +0,0 @@ -/******************************************************************************* - * 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.crypto; - -import java.io.InterruptedIOException; -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.util.concurrent.Callable; - -import org.cryptomator.crypto.engine.FileContentCryptor; -import org.cryptomator.crypto.engine.FileContentEncryptor; -import org.cryptomator.filesystem.WritableFile; - -class CiphertextWriter implements Callable { - - private final WritableFile file; - private final FileContentEncryptor encryptor; - - public CiphertextWriter(WritableFile file, FileContentEncryptor encryptor) { - this.file = file; - this.encryptor = encryptor; - } - - @Override - public Void call() throws InterruptedIOException { - try { - callInterruptibly(); - } catch (InterruptedException e) { - throw new InterruptedIOException("Task interrupted while waiting for ciphertext"); - } - return null; - } - - private void callInterruptibly() throws InterruptedException { - try { - ByteBuffer ciphertext; - while ((ciphertext = encryptor.ciphertext()) != FileContentCryptor.EOF) { - file.write(ciphertext); - } - } catch (UncheckedIOException e) { - encryptor.cancelWithException(e); - } - } - -} \ No newline at end of file diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/ConflictResolver.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/ConflictResolver.java deleted file mode 100644 index 9ef4142bf..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/ConflictResolver.java +++ /dev/null @@ -1,105 +0,0 @@ -package org.cryptomator.filesystem.crypto; - -import static org.cryptomator.filesystem.crypto.Constants.DIR_PREFIX; - -import java.nio.ByteBuffer; -import java.util.Optional; -import java.util.UUID; -import java.util.function.Function; -import java.util.regex.MatchResult; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apache.commons.lang3.StringUtils; -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.ReadableFile; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -final class ConflictResolver { - - private static final Logger LOG = LoggerFactory.getLogger(ConflictResolver.class); - private static final int UUID_FIRST_GROUP_STRLEN = 8; - private static final int MAX_DIR_FILE_SIZE = 87; // "normal" file header has 88 bytes - - private final Pattern encryptedNamePattern; - private final Function> nameDecryptor; - private final Function> nameEncryptor; - - public ConflictResolver(Pattern encryptedNamePattern, Function> nameDecryptor, Function> nameEncryptor) { - this.encryptedNamePattern = encryptedNamePattern; - this.nameDecryptor = nameDecryptor; - this.nameEncryptor = nameEncryptor; - } - - public File resolveIfNecessary(File file) { - Matcher m = encryptedNamePattern.matcher(StringUtils.removeStart(file.name(), DIR_PREFIX)); - if (m.matches()) { - // full match, use file as is - return file; - } else if (m.find(0)) { - // partial match, might be conflicting - return resolveConflict(file, m.toMatchResult()); - } else { - // no match, file not relevant - return file; - } - } - - private File resolveConflict(File conflictingFile, MatchResult matchResult) { - String ciphertext = matchResult.group(); - boolean isDirectory = conflictingFile.name().startsWith(DIR_PREFIX); - Optional cleartext = nameDecryptor.apply(ciphertext); - if (cleartext.isPresent()) { - Folder folder = conflictingFile.parent().get(); - File canonicalFile = folder.file(isDirectory ? DIR_PREFIX + ciphertext : ciphertext); - if (isDirectory && canonicalFile.exists() && isSameFileBasedOnSample(canonicalFile, conflictingFile, MAX_DIR_FILE_SIZE)) { - // there must not be two directories pointing to the same directory id. In this case no human interaction is needed to resolve this conflict: - conflictingFile.delete(); - return canonicalFile; - } else { - // conventional conflict detected! look for an alternative name: - File alternativeFile; - String conflictId; - do { - conflictId = createConflictId(); - String alternativeCleartext = cleartext.get() + " (Conflict " + conflictId + ")"; - String alternativeCiphertext = nameEncryptor.apply(alternativeCleartext).get(); - alternativeFile = folder.file(isDirectory ? DIR_PREFIX + alternativeCiphertext : alternativeCiphertext); - } while (alternativeFile.exists()); - LOG.debug("Detected conflict {}:\n{}\n{}", conflictId, canonicalFile, conflictingFile); - conflictingFile.moveTo(alternativeFile); - return alternativeFile; - } - } else { - // not decryptable; false positive - return conflictingFile; - } - } - - private boolean isSameFileBasedOnSample(File file1, File file2, int sampleSize) { - if (file1.size() != file2.size()) { - return false; - } else { - try (ReadableFile r1 = file1.openReadable(); ReadableFile r2 = file2.openReadable()) { - ByteBuffer beginOfFile1 = ByteBuffer.allocate(sampleSize); - ByteBuffer beginOfFile2 = ByteBuffer.allocate(sampleSize); - int bytesRead1 = r1.read(beginOfFile1); - int bytesRead2 = r2.read(beginOfFile2); - if (bytesRead1 == bytesRead2) { - beginOfFile1.flip(); - beginOfFile2.flip(); - return beginOfFile1.equals(beginOfFile2); - } else { - return false; - } - } - } - } - - private String createConflictId() { - return UUID.randomUUID().toString().substring(0, UUID_FIRST_GROUP_STRLEN); - } - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/Constants.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/Constants.java deleted file mode 100644 index eb552aad7..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/Constants.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.cryptomator.filesystem.crypto; - -public final class Constants { - - private Constants() { - } - - static final String DATA_ROOT_DIR = "d"; - static final String ROOT_DIRECOTRY_ID = ""; - - public static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; - public static final String MASTERKEY_BACKUP_FILENAME = "masterkey.cryptomator.bkup"; - - static final String DIR_PREFIX = "0"; - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFile.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFile.java deleted file mode 100644 index 9d2afa238..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFile.java +++ /dev/null @@ -1,116 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto; - -import static org.cryptomator.crypto.engine.impl.Constants.CHUNK_SIZE; -import static org.cryptomator.crypto.engine.impl.Constants.PAYLOAD_SIZE; - -import java.io.UncheckedIOException; -import java.nio.file.FileAlreadyExistsException; -import java.util.Optional; - -import org.cryptomator.crypto.engine.Cryptor; -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.filesystem.WritableFile; - -class CryptoFile extends CryptoNode implements File { - - public CryptoFile(CryptoFolder parent, String name, Cryptor cryptor) { - super(parent, name, cryptor); - } - - @Override - protected Optional encryptedName() { - return parent().get().encryptChildName(name()); - } - - @Override - public long size() throws UncheckedIOException { - if (!physicalFile().isPresent()) { - return -1l; - } else { - File file = physicalFile().get(); - long ciphertextSize = file.size() - cryptor.getFileContentCryptor().getHeaderSize(); - long overheadPerChunk = CHUNK_SIZE - PAYLOAD_SIZE; - long numFullChunks = ciphertextSize / CHUNK_SIZE; // floor by int-truncation - long additionalCiphertextBytes = ciphertextSize % CHUNK_SIZE; - if (additionalCiphertextBytes > 0 && additionalCiphertextBytes <= overheadPerChunk) { - throw new IllegalArgumentException("Method not defined for input value " + ciphertextSize); - } - long additionalCleartextBytes = (additionalCiphertextBytes == 0) ? 0 : additionalCiphertextBytes - overheadPerChunk; - assert additionalCleartextBytes >= 0; - return PAYLOAD_SIZE * numFullChunks + additionalCleartextBytes; - } - } - - @Override - public ReadableFile openReadable() { - boolean authenticate = !fileSystem().delegate().shouldSkipAuthentication(toString()); - ReadableFile physicalReadable = forceGetPhysicalFile().openReadable(); - boolean success = false; - try { - final ReadableFile result = new CryptoReadableFile(cryptor.getFileContentCryptor(), physicalReadable, authenticate, this::reportAuthError); - success = true; - return result; - } finally { - if (!success) { - physicalReadable.close(); - } - } - } - - private void reportAuthError() { - fileSystem().delegate().authenticationFailed(this.toString()); - } - - @Override - public WritableFile openWritable() { - if (parent.folder(name).exists()) { - throw new UncheckedIOException(new FileAlreadyExistsException(toString())); - } - WritableFile physicalWrtiable = forceGetPhysicalFile().openWritable(); - boolean success = false; - try { - final WritableFile result = new CryptoWritableFile(cryptor.getFileContentCryptor(), physicalWrtiable); - success = true; - return result; - } finally { - if (!success) { - physicalWrtiable.close(); - } - } - } - - @Override - public String toString() { - return parent.toString() + name; - } - - @Override - public int compareTo(File o) { - return toString().compareTo(o.toString()); - } - - @Override - public void delete() throws UncheckedIOException { - forceGetPhysicalFile().delete(); - } - - @Override - public void moveTo(File destination) throws UncheckedIOException { - if (destination instanceof CryptoFile) { - CryptoFile dst = (CryptoFile) destination; - forceGetPhysicalFile().moveTo(dst.forceGetPhysicalFile()); - } else { - throw new IllegalArgumentException("Can not move CryptoFile to conventional File."); - } - } - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystem.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystem.java deleted file mode 100644 index 892c96b5c..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystem.java +++ /dev/null @@ -1,108 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto; - -import static org.cryptomator.filesystem.crypto.Constants.DATA_ROOT_DIR; -import static org.cryptomator.filesystem.crypto.Constants.ROOT_DIRECOTRY_ID; - -import java.io.UncheckedIOException; -import java.time.Instant; -import java.util.Optional; - -import org.cryptomator.crypto.engine.Cryptor; -import org.cryptomator.crypto.engine.InvalidPassphraseException; -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.Folder; - -class CryptoFileSystem extends CryptoFolder implements FileSystem { - - private final Folder physicalRoot; - private final CryptoFileSystemDelegate delegate; - - public CryptoFileSystem(Folder physicalRoot, Cryptor cryptor, CryptoFileSystemDelegate delegate, CharSequence passphrase) throws InvalidPassphraseException { - super(null, "", cryptor); - if (cryptor.isDestroyed()) { - throw new IllegalArgumentException("Cryptor's keys must not be destroyed."); - } - this.physicalRoot = physicalRoot; - this.delegate = delegate; - create(); - } - - CryptoFileSystemDelegate delegate() { - return delegate; - } - - @Override - protected Optional getDirectoryId() { - return Optional.of(ROOT_DIRECOTRY_ID); - } - - @Override - protected Optional physicalFile() { - throw new UnsupportedOperationException("Crypto filesystem root doesn't provide a directory file, as the directory ID is fixed."); - } - - @Override - protected Folder physicalDataRoot() { - return physicalRoot.folder(DATA_ROOT_DIR); - } - - @Override - public Optional quotaUsedBytes() { - return physicalRoot.fileSystem().quotaUsedBytes(); - } - - @Override - public Optional quotaAvailableBytes() { - return physicalRoot.fileSystem().quotaAvailableBytes(); - } - - @Override - public Optional parent() { - return Optional.empty(); - } - - @Override - public boolean exists() { - return forceGetPhysicalFolder().exists(); - } - - @Override - public Optional creationTime() throws UncheckedIOException { - return forceGetPhysicalFolder().creationTime(); - } - - @Override - public Instant lastModified() { - return forceGetPhysicalFolder().lastModified(); - } - - @Override - public void delete() { - throw new UnsupportedOperationException("Can not delete CryptoFileSytem root."); - } - - @Override - public void moveTo(Folder target) { - throw new UnsupportedOperationException("Can not move CryptoFileSytem root."); - } - - @Override - public void create() { - forceGetPhysicalFolder().create(); - } - - @Override - public String toString() { - return "/"; - } - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystemDelegate.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystemDelegate.java deleted file mode 100644 index 9f3a55f5d..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystemDelegate.java +++ /dev/null @@ -1,29 +0,0 @@ -/******************************************************************************* - * 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.crypto; - -public interface CryptoFileSystemDelegate { - - /** - * Reports the path for resources, that could not be decrypted due to authentication errors. - * - * @param cleartextPath Unix-style vault-relative path - */ - void authenticationFailed(String cleartextPath); - - /** - * Allows the delegate to deactivate authentication during decryption. - * This bears the risk of CCAs, thus this method should only return true for data recovery purposes. - * - * @param cleartextPath Unix-style vault-relative path - * @return Must always default to false, except when authentication should be skipped. - */ - boolean shouldSkipAuthentication(String cleartextPath); - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystemFactory.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystemFactory.java deleted file mode 100644 index fad864529..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystemFactory.java +++ /dev/null @@ -1,61 +0,0 @@ -/******************************************************************************* - * 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.crypto; - -import static org.cryptomator.filesystem.crypto.Constants.MASTERKEY_FILENAME; - -import java.io.UncheckedIOException; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import org.cryptomator.crypto.engine.Cryptor; -import org.cryptomator.crypto.engine.InvalidPassphraseException; -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.Folder; - -@Singleton -public class CryptoFileSystemFactory { - - private final Masterkeys masterkeys; - private final BlockAlignedFileSystemFactory blockAlignedFileSystemFactory; - - @Inject - public CryptoFileSystemFactory(Masterkeys masterkeys, BlockAlignedFileSystemFactory blockAlignedFileSystemFactory) { - this.masterkeys = masterkeys; - this.blockAlignedFileSystemFactory = blockAlignedFileSystemFactory; - } - - public boolean isValidVaultStructure(Folder vaultLocation) { - return vaultLocation.file(MASTERKEY_FILENAME).exists(); - } - - public void initializeNew(Folder vaultLocation, CharSequence passphrase) { - masterkeys.initialize(vaultLocation, passphrase); - } - - public FileSystem unlockExisting(Folder vaultLocation, CharSequence passphrase, CryptoFileSystemDelegate delegate) throws InvalidPassphraseException { - final Cryptor cryptor = masterkeys.decrypt(vaultLocation, passphrase); - masterkeys.backup(vaultLocation); - final FileSystem cryptoFs = new CryptoFileSystem(vaultLocation, cryptor, delegate, passphrase); - return blockAlignedFileSystemFactory.get(cryptoFs); - } - - public void changePassphrase(Folder vaultLocation, CharSequence oldPassphrase, CharSequence newPassphrase) throws InvalidPassphraseException { - masterkeys.backup(vaultLocation); - try { - masterkeys.changePassphrase(vaultLocation, oldPassphrase, newPassphrase); - // At this point the backup is still using the old password. - // It will be changed as soon as the user unlocks the vault the next time. - // This way he can still restore the old password, if he doesn't remember the new one. - } catch (UncheckedIOException e) { - masterkeys.restoreBackup(vaultLocation); - } - } -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFolder.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFolder.java deleted file mode 100644 index af9698d12..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFolder.java +++ /dev/null @@ -1,243 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto; - -import static java.lang.String.format; -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.apache.commons.lang3.StringUtils.removeStart; -import static org.cryptomator.filesystem.crypto.Constants.DIR_PREFIX; - -import java.io.FileNotFoundException; -import java.io.UncheckedIOException; -import java.nio.file.FileAlreadyExistsException; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Predicate; -import java.util.regex.Pattern; -import java.util.stream.Stream; - -import org.apache.commons.lang3.StringUtils; -import org.cryptomator.common.LazyInitializer; -import org.cryptomator.common.WeakValuedCache; -import org.cryptomator.common.streams.AutoClosingStream; -import org.cryptomator.crypto.engine.CryptoException; -import org.cryptomator.crypto.engine.Cryptor; -import org.cryptomator.filesystem.Deleter; -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.Node; -import org.cryptomator.io.FileContents; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -class CryptoFolder extends CryptoNode implements Folder { - - private static final Logger LOG = LoggerFactory.getLogger(CryptoFolder.class); - private final WeakValuedCache folders = WeakValuedCache.usingLoader(this::newFolder); - private final WeakValuedCache files = WeakValuedCache.usingLoader(this::newFile); - private final AtomicReference directoryId = new AtomicReference<>(); - private final ConflictResolver conflictResolver; - - public CryptoFolder(CryptoFolder parent, String name, Cryptor cryptor) { - super(parent, name, cryptor); - this.conflictResolver = new ConflictResolver(cryptor.getFilenameCryptor().encryptedNamePattern(), this::decryptChildName, this::encryptChildName); - } - - /* ======================= name + directory id ======================= */ - - @Override - protected Optional encryptedName() { - if (parent().isPresent()) { - return parent().get().encryptChildName(name()).map(s -> DIR_PREFIX + s); - } else { - return Optional.of(DIR_PREFIX + cryptor.getFilenameCryptor().encryptFilename(name())); - } - } - - Optional physicalFolder() { - if (getDirectoryId().isPresent()) { - final String encryptedThenHashedDirId = cryptor.getFilenameCryptor().hashDirectoryId(getDirectoryId().get()); - return Optional.of(physicalDataRoot().folder(encryptedThenHashedDirId.substring(0, 2)).folder(encryptedThenHashedDirId.substring(2))); - } else { - return Optional.empty(); - } - } - - Folder forceGetPhysicalFolder() { - return physicalFolder().orElseThrow(() -> { - return new UncheckedIOException(new FileNotFoundException(toString())); - }); - } - - protected Optional getDirectoryId() { - return Optional.ofNullable(LazyInitializer.initializeLazily(directoryId, () -> { - return physicalFile().filter(File::exists).map(FileContents.UTF_8::readContents).orElse(null); - })); - } - - /* ======================= children ======================= */ - - @Override - public Stream children() { - return AutoClosingStream.from(Stream.concat(files(), folders())); - } - - private Stream nonConflictingFiles() { - if (exists()) { - final Stream files = physicalFolder().filter(Folder::exists).map(Folder::files).orElse(Stream.empty()); - return files.filter(startsWithEncryptedName()).map(conflictResolver::resolveIfNecessary).distinct(); - } else { - throw new UncheckedIOException(new FileNotFoundException(format("Folder %s does not exist", this))); - } - } - - private Predicate startsWithEncryptedName() { - final Pattern encryptedNamePattern = cryptor.getFilenameCryptor().encryptedNamePattern(); - return (File file) -> encryptedNamePattern.matcher(removeStart(file.name(),DIR_PREFIX)).find(); - } - - Optional decryptChildName(String ciphertextFileName) { - return getDirectoryId().map(s -> s.getBytes(UTF_8)).map(dirId -> { - try { - return cryptor.getFilenameCryptor().decryptFilename(ciphertextFileName, dirId); - } catch (CryptoException e) { - LOG.warn("Filename decryption of {} failed: {}", ciphertextFileName, e.getMessage()); - return null; - } - }); - } - - Optional encryptChildName(String cleartextFileName) { - return getDirectoryId().map(s -> s.getBytes(UTF_8)).map(dirId -> { - return cryptor.getFilenameCryptor().encryptFilename(cleartextFileName, dirId); - }); - } - - @Override - public Stream files() { - return nonConflictingFiles().map(File::name).filter(startsWithDirPrefix().negate()).map(this::decryptChildName).filter(Optional::isPresent).map(Optional::get).map(this::file); - } - - @Override - public Stream folders() { - return nonConflictingFiles().map(File::name).filter(startsWithDirPrefix()).map(this::removeDirPrefix).map(this::decryptChildName).filter(Optional::isPresent).map(Optional::get).map(this::folder); - } - - private Predicate startsWithDirPrefix() { - return (String encryptedFolderName) -> StringUtils.startsWith(encryptedFolderName, DIR_PREFIX); - } - - private String removeDirPrefix(String encryptedFolderName) { - return StringUtils.removeStart(encryptedFolderName, DIR_PREFIX); - } - - @Override - public CryptoFile file(String name) { - return files.get(name); - } - - @Override - public CryptoFolder folder(String name) { - return folders.get(name); - } - - private CryptoFile newFile(String name) { - return new CryptoFile(this, name, cryptor); - } - - private CryptoFolder newFolder(String name) { - return new CryptoFolder(this, name, cryptor); - } - - /* ======================= create/move/delete ======================= */ - - @Override - public void create() { - parent.create(); - final boolean newDirIdGiven = directoryId.compareAndSet(null, UUID.randomUUID().toString()); - final File dirFile = forceGetPhysicalFile(); - final Folder dir = forceGetPhysicalFolder(); - if (dirFile.exists() && dir.exists()) { - return; - } else if (!newDirIdGiven) { - throw new IllegalStateException("Newly created folder, that didn't exist before, already had an directoryId."); - } - if (parent.file(name).exists()) { - throw new UncheckedIOException(new FileAlreadyExistsException(parent.file(name).toString())); - } - FileContents.UTF_8.writeContents(dirFile, directoryId.get()); - dir.create(); - } - - @Override - public void moveTo(Folder target) { - if (target instanceof CryptoFolder) { - moveToInternal((CryptoFolder) target); - } else { - throw new UnsupportedOperationException("Can not move CryptoFolder to conventional folder."); - } - } - - private void moveToInternal(CryptoFolder target) { - if (this.isAncestorOf(target) || target.isAncestorOf(this)) { - throw new IllegalArgumentException("Can not move directories containing one another (src: " + this + ", dst: " + target + ")"); - } - assert target.parent().isPresent() : "Target can not be root, thus has a parent"; - - // prepare target: - target.delete(); - target.parent().get().create(); - - // perform the actual move: - final File dirFile = forceGetPhysicalFile(); - final String dirId = getDirectoryId().get(); - boolean dirIdMovedSuccessfully = target.directoryId.compareAndSet(null, dirId); - if (!dirIdMovedSuccessfully) { - throw new IllegalStateException("Target's directoryId wasn't null, even though it has been explicitly deleted."); - } - dirFile.moveTo(target.forceGetPhysicalFile()); - - // cut all ties: - this.invalidateDirectoryIdsRecursively(); - - assert !exists(); - assert target.exists(); - } - - @Override - public void delete() { - if (!exists()) { - assert directoryId.get() == null : "nonexisting folder still has a directory id"; - return; - } - Deleter.deleteContent(this); - Folder physicalFolder = forceGetPhysicalFolder(); - physicalFolder.delete(); - Folder physicalFolderParent = physicalFolder.parent().get(); - if (physicalFolderParent.exists() && physicalFolderParent.folders().count() == 0) { - physicalFolderParent.delete(); - } - forceGetPhysicalFile().delete(); - invalidateDirectoryIdsRecursively(); - } - - private void invalidateDirectoryIdsRecursively() { - directoryId.set(null); - folders.forEach((name, folder) -> { - folder.invalidateDirectoryIdsRecursively(); - }); - } - - @Override - public String toString() { - return parent.toString() + name + "/"; - } - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoNode.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoNode.java deleted file mode 100644 index 3ddb9ea42..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoNode.java +++ /dev/null @@ -1,122 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto; - -import java.io.FileNotFoundException; -import java.io.UncheckedIOException; -import java.time.Instant; -import java.util.Optional; - -import org.cryptomator.crypto.engine.Cryptor; -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.Node; - -abstract class CryptoNode implements Node { - - protected final CryptoFolder parent; - protected final String name; - protected final Cryptor cryptor; - - public CryptoNode(CryptoFolder parent, String name, Cryptor cryptor) { - this.parent = parent; - this.name = name; - this.cryptor = cryptor; - } - - protected Folder physicalDataRoot() { - return parent.physicalDataRoot(); - } - - protected abstract Optional encryptedName(); - - protected Optional physicalFile() { - if (parent.exists() && encryptedName().isPresent()) { - return Optional.of(parent.forceGetPhysicalFolder().file(encryptedName().get())); - } else { - return Optional.empty(); - } - } - - protected File forceGetPhysicalFile() { - return physicalFile().orElseThrow(() -> { - return new UncheckedIOException(new FileNotFoundException(toString())); - }); - } - - @Override - public CryptoFileSystem fileSystem() { - return (CryptoFileSystem) Node.super.fileSystem(); - } - - @Override - public Optional parent() { - return Optional.of(parent); - } - - @Override - public String name() { - return name; - } - - @Override - public boolean exists() { - return physicalFile().map(File::exists).orElse(false); - } - - @Override - public Instant lastModified() { - return forceGetPhysicalFile().lastModified(); - } - - @Override - public void setLastModified(Instant lastModified) throws UncheckedIOException { - forceGetPhysicalFile().setLastModified(lastModified); - } - - @Override - public Optional creationTime() throws UncheckedIOException { - return forceGetPhysicalFile().creationTime(); - } - - @Override - public void setCreationTime(Instant creationTime) throws UncheckedIOException { - forceGetPhysicalFile().setCreationTime(creationTime); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((name == null) ? 0 : name.hashCode()); - result = prime * result + ((parent == null) ? 0 : parent.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof CryptoNode) { - CryptoNode other = (CryptoNode) obj; - return this.getClass() == other.getClass() // - && (this.parent == null && other.parent == null || this.parent != null && this.parent.equals(other.parent)) // - && (this.name == null && other.name == null || this.name != null && this.name.equals(other.name)); - } else { - return false; - } - } - - /** - * Unix-style cleartext path rooted at the vault's top-level directory. - * - * @return Vault-relative cleartext path. - */ - @Override - public abstract String toString(); - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoReadableFile.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoReadableFile.java deleted file mode 100644 index 9792643f2..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoReadableFile.java +++ /dev/null @@ -1,111 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto; - -import java.io.IOException; -import java.io.InterruptedIOException; -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; - -import org.cryptomator.crypto.engine.AuthenticationFailedException; -import org.cryptomator.crypto.engine.FileContentCryptor; -import org.cryptomator.crypto.engine.FileContentDecryptor; -import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.io.ByteBuffers; - -class CryptoReadableFile implements ReadableFile { - - private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0); - - private final ExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); - private final ByteBuffer header; - private final FileContentCryptor cryptor; - private final ReadableFile file; - private final boolean authenticate; - private final Runnable onAuthError; - private FileContentDecryptor decryptor; - private Future readAheadTask; - private ByteBuffer bufferedCleartext = EMPTY_BUFFER; - - public CryptoReadableFile(FileContentCryptor cryptor, ReadableFile file, boolean authenticate, Runnable onAuthError) { - this.header = ByteBuffer.allocate(cryptor.getHeaderSize()); - this.cryptor = cryptor; - this.file = file; - this.authenticate = authenticate; - this.onAuthError = onAuthError; - file.position(0); - int headerBytesRead = file.read(header); - if (headerBytesRead != header.capacity()) { - throw new IllegalArgumentException("File too short to contain a header."); - } - header.flip(); - this.position(0); - } - - @Override - public int read(ByteBuffer target) { - try { - if (bufferedCleartext == FileContentCryptor.EOF) { - return -1; - } - int bytesRead = 0; - while (target.remaining() > 0 && bufferedCleartext != FileContentCryptor.EOF) { - bufferCleartext(); - bytesRead += readFromBufferedCleartext(target); - } - return bytesRead; - } catch (InterruptedException e) { - throw new UncheckedIOException(new InterruptedIOException("Task interrupted while waiting for cleartext")); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - @Override - public void position(long position) throws UncheckedIOException { - if (readAheadTask != null) { - readAheadTask.cancel(true); - bufferedCleartext = EMPTY_BUFFER; - } - long ciphertextPos = cryptor.toCiphertextPos(position); - decryptor = cryptor.createFileContentDecryptor(header.asReadOnlyBuffer(), ciphertextPos, authenticate); - readAheadTask = executorService.submit(new CiphertextReader(file, decryptor, header.remaining() + ciphertextPos)); - } - - private void bufferCleartext() throws InterruptedException, IOException { - if (!bufferedCleartext.hasRemaining()) { - try { - bufferedCleartext = decryptor.cleartext(); - } catch (AuthenticationFailedException e) { - onAuthError.run(); - throw new IOException("Failed to decrypt file due to an authentication error.", e); - } - } - } - - private int readFromBufferedCleartext(ByteBuffer target) { - assert bufferedCleartext != null; - return ByteBuffers.copy(bufferedCleartext, target); - } - - @Override - public boolean isOpen() { - return file.isOpen(); - } - - @Override - public void close() { - executorService.shutdownNow(); - file.close(); - } - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoWritableFile.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoWritableFile.java deleted file mode 100644 index 0abf67ff6..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoWritableFile.java +++ /dev/null @@ -1,113 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto; - -import java.io.IOException; -import java.io.InterruptedIOException; -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.util.Optional; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; - -import org.cryptomator.crypto.engine.FileContentCryptor; -import org.cryptomator.crypto.engine.FileContentEncryptor; -import org.cryptomator.filesystem.WritableFile; -import org.cryptomator.io.ByteBuffers; - -class CryptoWritableFile implements WritableFile { - - final WritableFile file; - private final ExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); - private final FileContentCryptor cryptor; - - private FileContentEncryptor encryptor; - private Future writeTask; - - public CryptoWritableFile(FileContentCryptor cryptor, WritableFile file) { - this.file = file; - this.cryptor = cryptor; - initialize(0); - } - - private void initialize(long firstCleartextByte) { - encryptor = cryptor.createFileContentEncryptor(Optional.empty(), firstCleartextByte); - writeHeader(); // write header with "zero content length" to avoid read access while still writing - writeTask = executorService.submit(new CiphertextWriter(file, encryptor)); - } - - private void writeHeader() { - ByteBuffer header = encryptor.getHeader(); - header.rewind(); - file.position(0); - file.write(header); - } - - @Override - public int write(ByteBuffer source) { - final int size = source.remaining(); - final ByteBuffer cleartextCopy = ByteBuffer.allocate(size); - ByteBuffers.copy(source, cleartextCopy); - cleartextCopy.flip(); - try { - encryptor.append(cleartextCopy); - return size; - } catch (InterruptedException e) { - throw new UncheckedIOException(new InterruptedIOException("Task interrupted while waiting for encryptor capacity")); - } - } - - @Override - public void position(long position) { - throw new UnsupportedOperationException("Partial write not implemented yet."); - } - - @Override - public void truncate() { - terminateAndWaitForWriteTask(); - file.truncate(); - initialize(0); - } - - @Override - public boolean isOpen() { - return file.isOpen(); - } - - @Override - public void close() { - try { - if (file.isOpen()) { - terminateAndWaitForWriteTask(); - writeHeader(); - } - } finally { - executorService.shutdownNow(); - file.close(); - } - } - - private void terminateAndWaitForWriteTask() { - try { - encryptor.append(FileContentCryptor.EOF); - writeTask.get(); - } catch (ExecutionException e) { - if (e.getCause() instanceof UncheckedIOException || e.getCause() instanceof IOException) { - throw new UncheckedIOException(new IOException(e)); - } else { - throw new IllegalStateException("Unexpected exception while waiting for encrypted file to be written", e); - } - } catch (InterruptedException e) { - throw new UncheckedIOException(new InterruptedIOException("Task interrupted while flushing encrypted content")); - } - } - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/Masterkeys.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/Masterkeys.java deleted file mode 100644 index 98b488836..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/Masterkeys.java +++ /dev/null @@ -1,112 +0,0 @@ -/******************************************************************************* - * 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.crypto; - -import static org.cryptomator.filesystem.crypto.Constants.MASTERKEY_BACKUP_FILENAME; -import static org.cryptomator.filesystem.crypto.Constants.MASTERKEY_FILENAME; - -import java.io.IOException; -import java.io.InputStream; -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.nio.channels.Channels; -import java.nio.channels.ReadableByteChannel; - -import javax.inject.Inject; -import javax.inject.Provider; -import javax.inject.Singleton; - -import org.apache.commons.io.IOUtils; -import org.cryptomator.crypto.engine.Cryptor; -import org.cryptomator.crypto.engine.InvalidPassphraseException; -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.WritableFile; - -@Singleton -class Masterkeys { - - private final Provider cryptorProvider; - - @Inject - public Masterkeys(Provider cryptorProvider) { - this.cryptorProvider = cryptorProvider; - } - - public void initialize(Folder vaultLocation, CharSequence passphrase) { - File masterkeyFile = vaultLocation.file(MASTERKEY_FILENAME); - Cryptor cryptor = cryptorProvider.get(); - try { - cryptor.randomizeMasterkey(); - writeMasterKey(masterkeyFile, cryptor, passphrase); - } finally { - cryptor.destroy(); - } - } - - public Cryptor decrypt(Folder vaultLocation, CharSequence passphrase) throws InvalidPassphraseException { - File masterkeyFile = vaultLocation.file(MASTERKEY_FILENAME); - Cryptor cryptor = cryptorProvider.get(); - boolean success = false; - try { - readMasterKey(masterkeyFile, cryptor, passphrase); - success = true; - } finally { - if (!success) { - cryptor.destroy(); - } - } - return cryptor; - } - - public void changePassphrase(Folder vaultLocation, CharSequence oldPassphrase, CharSequence newPassphrase) throws InvalidPassphraseException { - File masterkeyFile = vaultLocation.file(MASTERKEY_FILENAME); - Cryptor cryptor = cryptorProvider.get(); - try { - readMasterKey(masterkeyFile, cryptor, oldPassphrase); - writeMasterKey(masterkeyFile, cryptor, newPassphrase); - } finally { - cryptor.destroy(); - } - } - - public void backup(Folder vaultLocation) { - File masterkeyFile = vaultLocation.file(MASTERKEY_FILENAME); - File backupFile = vaultLocation.file(MASTERKEY_BACKUP_FILENAME); - masterkeyFile.copyTo(backupFile); - } - - public void restoreBackup(Folder vaultLocation) { - File backupFile = vaultLocation.file(MASTERKEY_BACKUP_FILENAME); - File masterkeyFile = vaultLocation.file(MASTERKEY_FILENAME); - backupFile.copyTo(masterkeyFile); - } - - /* I/O */ - - private static void readMasterKey(File file, Cryptor cryptor, CharSequence passphrase) throws UncheckedIOException, InvalidPassphraseException { - try ( // - ReadableByteChannel channel = file.openReadable(); // - InputStream in = Channels.newInputStream(channel)) { - final byte[] fileContents = IOUtils.toByteArray(in); - cryptor.readKeysFromMasterkeyFile(fileContents, passphrase); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - private static void writeMasterKey(File file, Cryptor cryptor, CharSequence passphrase) throws UncheckedIOException { - try (WritableFile writable = file.openWritable()) { - writable.truncate(); - final byte[] fileContents = cryptor.writeKeysToMasterkeyFile(passphrase); - writable.write(ByteBuffer.wrap(fileContents)); - } - } - -} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/package-info.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/package-info.java deleted file mode 100644 index e97b4032f..000000000 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/package-info.java +++ /dev/null @@ -1,14 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 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 - *******************************************************************************/ -/** - * Provides a decoration layer for the {@link org.cryptomator.filesystem Filesystem API}, consuming an encrypted file system and providing access to a cleartext filesystem. - * While the implementation in this package dictates the Vault directory layout, no encryption code can be found here. - * All cryptographic operations are delegated to the {@link org.cryptomator.crypto.engine CryptoEngine}. - */ -package org.cryptomator.filesystem.crypto; \ No newline at end of file diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/NoCryptor.java b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/NoCryptor.java deleted file mode 100644 index 38cc2f644..000000000 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/NoCryptor.java +++ /dev/null @@ -1,47 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto.engine; - -public class NoCryptor implements Cryptor { - - private final FilenameCryptor filenameCryptor = new NoFilenameCryptor(); - private final FileContentCryptor fileContentCryptor = new NoFileContentCryptor(); - - @Override - public FilenameCryptor getFilenameCryptor() { - return filenameCryptor; - } - - @Override - public FileContentCryptor getFileContentCryptor() { - return fileContentCryptor; - } - - @Override - public void randomizeMasterkey() { - // like this? https://xkcd.com/221/ - } - - @Override - public void readKeysFromMasterkeyFile(byte[] masterkeyFileContents, CharSequence passphrase) { - // thanks, but I don't need a key, if I'm not encryption anything... - } - - @Override - public byte[] writeKeysToMasterkeyFile(CharSequence passphrase) { - // ok, if you insist to get my non-existing key data... here you go: - return new byte[0]; - } - - @Override - public void destroy() { - // no-op - } - -} diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/NoFileContentCryptor.java b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/NoFileContentCryptor.java deleted file mode 100644 index 5a342d6e4..000000000 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/NoFileContentCryptor.java +++ /dev/null @@ -1,135 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto.engine; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.util.Optional; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.function.Supplier; - -class NoFileContentCryptor implements FileContentCryptor { - - @Override - public int getHeaderSize() { - return Long.BYTES; - } - - @Override - public long toCiphertextPos(long cleartextPos) { - return cleartextPos; - } - - @Override - public FileContentDecryptor createFileContentDecryptor(ByteBuffer header, long firstCiphertextByte, boolean authenticate) { - if (header.remaining() != getHeaderSize()) { - throw new IllegalArgumentException("Invalid header size."); - } - return new Decryptor(header); - } - - @Override - public FileContentEncryptor createFileContentEncryptor(Optional header, long firstCleartextByte) { - return new Encryptor(); - } - - private class Decryptor implements FileContentDecryptor { - - private final BlockingQueue> cleartextQueue = new LinkedBlockingQueue<>(); - - private Decryptor(ByteBuffer header) { - assert header.remaining() == Long.BYTES; - } - - @Override - public void append(ByteBuffer ciphertext) { - try { - if (ciphertext == FileContentCryptor.EOF) { - cleartextQueue.put(() -> FileContentCryptor.EOF); - } else { - cleartextQueue.put(ciphertext::asReadOnlyBuffer); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - @Override - public void cancelWithException(Exception cause) throws InterruptedException { - cleartextQueue.put(() -> { - throw new UncheckedIOException(new IOException(cause)); - }); - } - - @Override - public ByteBuffer cleartext() throws InterruptedException { - return cleartextQueue.take().get(); - } - - @Override - public void destroy() { - // no-op - } - - } - - private class Encryptor implements FileContentEncryptor { - - private final BlockingQueue> ciphertextQueue = new LinkedBlockingQueue<>(); - private long numCleartextBytesEncrypted = 0; - - @Override - public ByteBuffer getHeader() { - ByteBuffer buf = ByteBuffer.allocate(Long.BYTES); - buf.putLong(numCleartextBytesEncrypted); - return buf; - } - - @Override - public int getHeaderSize() { - return Long.BYTES; - } - - @Override - public void append(ByteBuffer cleartext) { - try { - if (cleartext == FileContentCryptor.EOF) { - ciphertextQueue.put(() -> FileContentCryptor.EOF); - } else { - int cleartextLen = cleartext.remaining(); - ciphertextQueue.put(cleartext::asReadOnlyBuffer); - numCleartextBytesEncrypted += cleartextLen; - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - @Override - public void cancelWithException(Exception cause) throws InterruptedException { - ciphertextQueue.put(() -> { - throw new UncheckedIOException(new IOException(cause)); - }); - } - - @Override - public ByteBuffer ciphertext() throws InterruptedException { - return ciphertextQueue.take().get(); - } - - @Override - public void destroy() { - // no-op - } - - } - -} diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/NoFilenameCryptor.java b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/NoFilenameCryptor.java deleted file mode 100644 index 590582ba8..000000000 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/NoFilenameCryptor.java +++ /dev/null @@ -1,67 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto.engine; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.regex.Pattern; - -import org.apache.commons.codec.binary.Base32; -import org.apache.commons.codec.binary.BaseNCodec; - -class NoFilenameCryptor implements FilenameCryptor { - - private static final BaseNCodec BASE32 = new Base32(); - private static final Pattern WILDCARD_PATTERN = Pattern.compile(".*"); - private static final ThreadLocal SHA1 = new ThreadLocalSha1(); - - @Override - public String hashDirectoryId(String cleartextDirectoryId) { - final byte[] cleartextBytes = cleartextDirectoryId.getBytes(UTF_8); - final byte[] hashedBytes = SHA1.get().digest(cleartextBytes); - return BASE32.encodeAsString(hashedBytes); - } - - @Override - public Pattern encryptedNamePattern() { - return WILDCARD_PATTERN; - } - - @Override - public String encryptFilename(String cleartextName, byte[]... associatedData) { - return cleartextName; - } - - @Override - public String decryptFilename(String ciphertextName, byte[]... associatedData) { - return ciphertextName; - } - - private static class ThreadLocalSha1 extends ThreadLocal { - - @Override - protected MessageDigest initialValue() { - try { - return MessageDigest.getInstance("SHA-1"); - } catch (NoSuchAlgorithmException e) { - throw new AssertionError("SHA-1 exists in every JVM"); - } - } - - @Override - public MessageDigest get() { - final MessageDigest sha1 = super.get(); - sha1.reset(); - return sha1; - } - } - -} diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/CryptorImplTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/CryptorImplTest.java deleted file mode 100644 index cb30b9bd5..000000000 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/CryptorImplTest.java +++ /dev/null @@ -1,100 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto.engine.impl; - -import java.io.IOException; - -import org.cryptomator.crypto.engine.Cryptor; -import org.cryptomator.crypto.engine.InvalidPassphraseException; -import org.cryptomator.crypto.engine.UnsupportedVaultFormatException; -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; - -public class CryptorImplTest { - - @Test - public void testMasterkeyDecryptionWithCorrectPassphrase() throws IOException { - final String testMasterKey = "{\"version\":5,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," // - + "\"primaryMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," // - + "\"hmacMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," // - + "\"versionMac\":\"Z9J8Uc5K1f7YKckLUFpXG39NHK1qUjzadw5nvOqvfok=\"}"; - final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl(); - cryptor.readKeysFromMasterkeyFile(testMasterKey.getBytes(), "asd"); - } - - @Test(expected = InvalidPassphraseException.class) - public void testMasterkeyDecryptionWithWrongPassphrase() throws IOException { - final String testMasterKey = "{\"version\":5,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," // - + "\"primaryMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," // - + "\"hmacMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," // - + "\"versionMac\":\"Z9J8Uc5K1f7YKckLUFpXG39NHK1qUjzadw5nvOqvfok=\"}"; - final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl(); - cryptor.readKeysFromMasterkeyFile(testMasterKey.getBytes(), "qwe"); - } - - @Test(expected = UnsupportedVaultFormatException.class) - public void testMasterkeyDecryptionWithWrongVaultFormat() throws IOException { - final String testMasterKey = "{\"version\":-1,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," // - + "\"primaryMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," // - + "\"hmacMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," // - + "\"versionMac\":\"Z9J8Uc5K1f7YKckLUFpXG39NHK1qUjzadw5nvOqvfok=\"}"; - final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl(); - cryptor.readKeysFromMasterkeyFile(testMasterKey.getBytes(), "asd"); - } - - @Ignore - @Test(expected = UnsupportedVaultFormatException.class) - public void testMasterkeyDecryptionWithMissingVersionMac() throws IOException { - final String testMasterKey = "{\"version\":5,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," // - + "\"primaryMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," // - + "\"hmacMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"}"; - final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl(); - cryptor.readKeysFromMasterkeyFile(testMasterKey.getBytes(), "asd"); - } - - @Ignore - @Test(expected = UnsupportedVaultFormatException.class) - public void testMasterkeyDecryptionWithWrongVersionMac() throws IOException { - final String testMasterKey = "{\"version\":5,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," // - + "\"primaryMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," // - + "\"hmacMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," // - + "\"versionMac\":\"z9J8Uc5K1f7YKckLUFpXG39NHK1qUjzadw5nvOqvfoK=\"}"; - final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl(); - cryptor.readKeysFromMasterkeyFile(testMasterKey.getBytes(), "asd"); - } - - @Test - public void testMasterkeyEncryption() throws IOException { - final String expectedMasterKey = "{\"version\":5,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":16384,\"scryptBlockSize\":8," // - + "\"primaryMasterKey\":\"BJPIq5pvhN24iDtPJLMFPLaVJWdGog9k4n0P03j4ru+ivbWY9OaRGQ==\"," // - + "\"hmacMasterKey\":\"BJPIq5pvhN24iDtPJLMFPLaVJWdGog9k4n0P03j4ru+ivbWY9OaRGQ==\"," // - + "\"versionMac\":\"yuwoRE9GSdgQ2b//qRpTCj3W0qsVLxYVa7/KB3PkfA4=\"}"; - final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl(); - cryptor.randomizeMasterkey(); - final byte[] masterkeyFile = cryptor.writeKeysToMasterkeyFile("asd"); - Assert.assertArrayEquals(expectedMasterKey.getBytes(), masterkeyFile); - } - - @Test - public void testGetFilenameAndFileContentCryptor() throws InterruptedException { - final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl(); - cryptor.randomizeMasterkey(); - - Assert.assertSame(cryptor.getFilenameCryptor(), cryptor.getFilenameCryptor()); - Assert.assertSame(cryptor.getFileContentCryptor(), cryptor.getFileContentCryptor()); - } - - @Test(expected = IllegalStateException.class) - public void testGetFilenameAndFileContentCryptorWithoutKeys() throws InterruptedException { - final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl(); - cryptor.getFilenameCryptor(); - } - -} diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FifoParallelDataProcessorTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FifoParallelDataProcessorTest.java deleted file mode 100644 index 69937f3cb..000000000 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FifoParallelDataProcessorTest.java +++ /dev/null @@ -1,138 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto.engine.impl; - -import java.lang.reflect.Field; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.junit.Assert; -import org.junit.Test; - -public class FifoParallelDataProcessorTest { - - @Test(expected = ExecutionException.class) - public void testRethrowsExceptionAsExecutionException() throws InterruptedException, ExecutionException { - ExecutorService exec = Executors.newSingleThreadExecutor(); - FifoParallelDataProcessor processor = new FifoParallelDataProcessor<>(1, exec); - try { - processor.submit(() -> { - throw new Exception("will be wrapped in a ExecutionException during 'processedData()'"); - }); - } catch (Exception e) { - Assert.fail("Exception must not yet be thrown."); - } - processor.processedData(); - exec.shutdownNow(); - } - - @Test(expected = RejectedExecutionException.class) - public void testRejectExecutionAfterShutdown() throws InterruptedException, ReflectiveOperationException, SecurityException { - ExecutorService exec = Executors.newSingleThreadExecutor(); - FifoParallelDataProcessor processor = new FifoParallelDataProcessor<>(1, exec); - Field field = FifoParallelDataProcessor.class.getDeclaredField("executorService"); - field.setAccessible(true); - ExecutorService executorService = (ExecutorService) field.get(processor); - executorService.shutdownNow(); - processor.submit(new IntegerJob(0, 1)); - exec.shutdownNow(); - } - - @Test - public void testStrictFifoOrder() throws InterruptedException, ExecutionException { - ExecutorService exec = Executors.newFixedThreadPool(4); - FifoParallelDataProcessor processor = new FifoParallelDataProcessor<>(10, exec); - processor.submit(new IntegerJob(100, 1)); - processor.submit(new IntegerJob(50, 2)); - processor.submitPreprocessed(3); - processor.submit(new IntegerJob(10, 4)); - processor.submit(new IntegerJob(10, 5)); - processor.submitPreprocessed(6); - - Assert.assertEquals(1, (int) processor.processedData()); - Assert.assertEquals(2, (int) processor.processedData()); - Assert.assertEquals(3, (int) processor.processedData()); - Assert.assertEquals(4, (int) processor.processedData()); - Assert.assertEquals(5, (int) processor.processedData()); - Assert.assertEquals(6, (int) processor.processedData()); - exec.shutdownNow(); - } - - @Test - public void testBlockingBehaviour() throws InterruptedException, ExecutionException { - ExecutorService exec = Executors.newSingleThreadExecutor(); - FifoParallelDataProcessor processor = new FifoParallelDataProcessor<>(1, exec); - processor.submitPreprocessed(1); // #1 in queue - - Thread t1 = new Thread(() -> { - try { - processor.submitPreprocessed(2); // #2 in queue - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - }); - t1.start(); - t1.join(10); - // job 2 should not have been submitted by now, thus t1 is still alive - Assert.assertTrue(t1.isAlive()); - Assert.assertEquals(1, (int) processor.processedData()); - Assert.assertEquals(2, (int) processor.processedData()); - t1.join(); - exec.shutdownNow(); - } - - @Test - public void testInterruptionDuringSubmission() throws InterruptedException, ExecutionException { - ExecutorService exec = Executors.newSingleThreadExecutor(); - FifoParallelDataProcessor processor = new FifoParallelDataProcessor<>(1, exec); - processor.submitPreprocessed(1); // #1 in queue - - final AtomicBoolean interruptedExceptionThrown = new AtomicBoolean(false); - Thread t1 = new Thread(() -> { - try { - processor.submitPreprocessed(2); // #2 in queue - } catch (InterruptedException e) { - interruptedExceptionThrown.set(true); - Thread.currentThread().interrupt(); - } - }); - t1.start(); - t1.join(10); - t1.interrupt(); - t1.join(10); - // job 2 should not have been submitted by now, thus t1 is still alive - Assert.assertFalse(t1.isAlive()); - Assert.assertTrue(interruptedExceptionThrown.get()); - Assert.assertEquals(1, (int) processor.processedData()); - exec.shutdownNow(); - } - - private static class IntegerJob implements Callable { - - private final long waitMillis; - private final int result; - - public IntegerJob(long waitMillis, int result) { - this.waitMillis = waitMillis; - this.result = result; - } - - @Override - public Integer call() throws Exception { - Thread.sleep(waitMillis); - return result; - } - - } - -} diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentCryptorImplTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentCryptorImplTest.java deleted file mode 100644 index 6fa92c74b..000000000 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentCryptorImplTest.java +++ /dev/null @@ -1,189 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto.engine.impl; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.security.SecureRandom; -import java.util.Arrays; -import java.util.Optional; - -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import org.cryptomator.crypto.engine.FileContentCryptor; -import org.cryptomator.crypto.engine.FileContentDecryptor; -import org.cryptomator.crypto.engine.FileContentEncryptor; -import org.cryptomator.io.ByteBuffers; -import org.junit.Assert; -import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class FileContentCryptorImplTest { - - private static final Logger LOG = LoggerFactory.getLogger(FileContentCryptorImplTest.class); - - private static final SecureRandom RANDOM_MOCK = new SecureRandom() { - - @Override - public void nextBytes(byte[] bytes) { - Arrays.fill(bytes, (byte) 0x00); - } - - }; - - @Test(expected = IllegalArgumentException.class) - public void testShortHeaderInDecryptor() throws InterruptedException { - final byte[] keyBytes = new byte[32]; - final SecretKey encryptionKey = new SecretKeySpec(keyBytes, "AES"); - final SecretKey macKey = new SecretKeySpec(keyBytes, "HmacSHA256"); - FileContentCryptor cryptor = new FileContentCryptorImpl(encryptionKey, macKey, RANDOM_MOCK); - - ByteBuffer tooShortHeader = ByteBuffer.allocate(63); - cryptor.createFileContentDecryptor(tooShortHeader, 0, true); - } - - @Test(expected = IllegalArgumentException.class) - public void testShortHeaderInEncryptor() throws InterruptedException { - final byte[] keyBytes = new byte[32]; - final SecretKey encryptionKey = new SecretKeySpec(keyBytes, "AES"); - final SecretKey macKey = new SecretKeySpec(keyBytes, "HmacSHA256"); - FileContentCryptor cryptor = new FileContentCryptorImpl(encryptionKey, macKey, RANDOM_MOCK); - - ByteBuffer tooShortHeader = ByteBuffer.allocate(63); - cryptor.createFileContentEncryptor(Optional.of(tooShortHeader), 0); - } - - @Test(expected = IllegalArgumentException.class) - public void testInvalidStartingPointInDecryptor() throws InterruptedException { - final byte[] keyBytes = new byte[32]; - final SecretKey encryptionKey = new SecretKeySpec(keyBytes, "AES"); - final SecretKey macKey = new SecretKeySpec(keyBytes, "HmacSHA256"); - FileContentCryptor cryptor = new FileContentCryptorImpl(encryptionKey, macKey, RANDOM_MOCK); - - ByteBuffer header = ByteBuffer.allocate(cryptor.getHeaderSize()); - cryptor.createFileContentDecryptor(header, 3, true); - } - - @Test(expected = IllegalArgumentException.class) - public void testInvalidStartingPointEncryptor() throws InterruptedException { - final byte[] keyBytes = new byte[32]; - final SecretKey encryptionKey = new SecretKeySpec(keyBytes, "AES"); - final SecretKey macKey = new SecretKeySpec(keyBytes, "HmacSHA256"); - FileContentCryptor cryptor = new FileContentCryptorImpl(encryptionKey, macKey, RANDOM_MOCK); - - cryptor.createFileContentEncryptor(Optional.empty(), 3); - } - - @Test - public void testEncryptionAndDecryption() throws InterruptedException { - final byte[] keyBytes = new byte[32]; - final SecretKey encryptionKey = new SecretKeySpec(keyBytes, "AES"); - final SecretKey macKey = new SecretKeySpec(keyBytes, "HmacSHA256"); - FileContentCryptor cryptor = new FileContentCryptorImpl(encryptionKey, macKey, RANDOM_MOCK); - - ByteBuffer header = ByteBuffer.allocate(cryptor.getHeaderSize()); - ByteBuffer ciphertext = ByteBuffer.allocate(100); - try (FileContentEncryptor encryptor = cryptor.createFileContentEncryptor(Optional.empty(), 0)) { - encryptor.append(ByteBuffer.wrap("cleartext message".getBytes())); - encryptor.append(FileContentCryptor.EOF); - ByteBuffer buf; - while ((buf = encryptor.ciphertext()) != FileContentCryptor.EOF) { - ByteBuffers.copy(buf, ciphertext); - } - ByteBuffers.copy(encryptor.getHeader(), header); - } - header.flip(); - ciphertext.flip(); - - ByteBuffer plaintext = ByteBuffer.allocate(100); - try (FileContentDecryptor decryptor = cryptor.createFileContentDecryptor(header, 0, true)) { - decryptor.append(ciphertext); - decryptor.append(FileContentCryptor.EOF); - ByteBuffer buf; - while ((buf = decryptor.cleartext()) != FileContentCryptor.EOF) { - ByteBuffers.copy(buf, plaintext); - } - } - plaintext.flip(); - - byte[] result = new byte[plaintext.remaining()]; - plaintext.get(result); - Assert.assertArrayEquals("cleartext message".getBytes(), result); - } - - @Test(timeout = 20000) // assuming a minimum speed of 10mb/s during encryption and decryption 20s should be enough - public void testEncryptionAndDecryptionSpeed() throws InterruptedException, IOException { - final byte[] keyBytes = new byte[32]; - final SecretKey encryptionKey = new SecretKeySpec(keyBytes, "AES"); - final SecretKey macKey = new SecretKeySpec(keyBytes, "HmacSHA256"); - final FileContentCryptor cryptor = new FileContentCryptorImpl(encryptionKey, macKey, RANDOM_MOCK); - final Path tmpFile = Files.createTempFile("encrypted", ".tmp"); - - final Thread fileWriter; - final ByteBuffer header; - final long encStart = System.nanoTime(); - try (FileContentEncryptor encryptor = cryptor.createFileContentEncryptor(Optional.empty(), 0)) { - fileWriter = new Thread(() -> { - try (FileChannel fc = FileChannel.open(tmpFile, StandardOpenOption.WRITE)) { - ByteBuffer ciphertext; - while ((ciphertext = encryptor.ciphertext()) != FileContentCryptor.EOF) { - fc.write(ciphertext); - } - } catch (Exception e) { - e.printStackTrace(); - } - }); - fileWriter.start(); - - final ByteBuffer cleartext = ByteBuffer.allocate(100000); // 100k - for (int i = 0; i < 1000; i++) { // 100M total - cleartext.rewind(); - encryptor.append(cleartext); - } - encryptor.append(FileContentCryptor.EOF); - header = encryptor.getHeader(); - } - fileWriter.join(); - final long encEnd = System.nanoTime(); - LOG.debug("Encryption of 100M took {}ms", (encEnd - encStart) / 1000 / 1000); - - final Thread fileReader; - final long decStart = System.nanoTime(); - try (FileContentDecryptor decryptor = cryptor.createFileContentDecryptor(header, 0, true)) { - fileReader = new Thread(() -> { - try (FileChannel fc = FileChannel.open(tmpFile, StandardOpenOption.READ)) { - ByteBuffer ciphertext = ByteBuffer.allocate(654321); - while (fc.read(ciphertext) != -1) { - ciphertext.flip(); - decryptor.append(ciphertext); - ciphertext.clear(); - } - decryptor.append(FileContentCryptor.EOF); - } catch (Exception e) { - e.printStackTrace(); - } - }); - fileReader.start(); - - while (decryptor.cleartext() != FileContentCryptor.EOF) { - // no-op - } - } - fileReader.join(); - final long decEnd = System.nanoTime(); - LOG.debug("Decryption of 100M took {}ms", (decEnd - decStart) / 1000 / 1000); - Files.delete(tmpFile); - } -} diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImplTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImplTest.java deleted file mode 100644 index 631811da7..000000000 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImplTest.java +++ /dev/null @@ -1,195 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto.engine.impl; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.security.SecureRandom; -import java.util.Arrays; -import java.util.Optional; - -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import org.bouncycastle.util.encoders.Base64; -import org.cryptomator.crypto.engine.AuthenticationFailedException; -import org.cryptomator.crypto.engine.FileContentCryptor; -import org.cryptomator.crypto.engine.FileContentDecryptor; -import org.cryptomator.crypto.engine.FileContentEncryptor; -import org.cryptomator.io.ByteBuffers; -import org.junit.Assert; -import org.junit.Test; - -public class FileContentDecryptorImplTest { - - private static final SecureRandom RANDOM_MOCK = new SecureRandom() { - - private static final long serialVersionUID = 1505563778398085504L; - - @Override - public void nextBytes(byte[] bytes) { - Arrays.fill(bytes, (byte) 0x00); - } - - }; - - @Test - public void testDecryption() throws InterruptedException { - final byte[] keyBytes = new byte[32]; - final SecretKey headerKey = new SecretKeySpec(keyBytes, "AES"); - final SecretKey macKey = new SecretKeySpec(keyBytes, "HmacSHA256"); - final byte[] header = Base64.decode("AAAAAAAAAAAAAAAAAAAAACNqP4ddv3Z2rUiiFJKEIIdTD4r7x0U2ualjtPHEy3OLzqdAPU1ga24VjC86+zlHN49BfMdzvHF3f9EE0LSnRLSsu6ps3IRcJg=="); - final byte[] content = Base64.decode("AAAAAAAAAAAAAAAAAAAAALTwrBTNYP7m3yTGKlhka9WPvX1Lpn5EYfVxlyX1ISgRXtdRnivM7r6F3Og="); - - try (FileContentDecryptor decryptor = new FileContentDecryptorImpl(headerKey, macKey, ByteBuffer.wrap(header), 0, true)) { - decryptor.append(ByteBuffer.wrap(Arrays.copyOfRange(content, 0, 15))); - decryptor.append(ByteBuffer.wrap(Arrays.copyOfRange(content, 15, 59))); - decryptor.append(FileContentCryptor.EOF); - - ByteBuffer result = ByteBuffer.allocate(11); // we just care about the first 11 bytes, as this is the ciphertext. - ByteBuffer buf; - while ((buf = decryptor.cleartext()) != FileContentCryptor.EOF) { - ByteBuffers.copy(buf, result); - } - - Assert.assertArrayEquals("hello world".getBytes(), result.array()); - } - } - - @Test(expected = AuthenticationFailedException.class) - public void testManipulatedHeaderDecryption() throws InterruptedException { - final byte[] keyBytes = new byte[32]; - final SecretKey headerKey = new SecretKeySpec(keyBytes, "AES"); - final SecretKey macKey = new SecretKeySpec(keyBytes, "HmacSHA256"); - final byte[] header = Base64.decode("AAAAAAAAAAAAAAAAAAAAACNqP4ddv3Z2rUiiFJKEIIdTD4r7x0U2ualjtPHEy3OLzqdAPU1ga24VjC86+zlHN49BfMdzvHF3f9EE0LSnRLSsu6ps3IRcJG=="); - - try (FileContentDecryptor decryptor = new FileContentDecryptorImpl(headerKey, macKey, ByteBuffer.wrap(header), 0, true)) { - - } - } - - @Test(expected = AuthenticationFailedException.class) - public void testManipulatedContentDecryption() throws InterruptedException { - final byte[] keyBytes = new byte[32]; - final SecretKey headerKey = new SecretKeySpec(keyBytes, "AES"); - final SecretKey macKey = new SecretKeySpec(keyBytes, "HmacSHA256"); - final byte[] header = Base64.decode("AAAAAAAAAAAAAAAAAAAAACNqP4ddv3Z2rUiiFJKEIIdTD4r7x0U2ualjtPHEy3OLzqdAPU1ga24VjC86+zlHN49BfMdzvHF3f9EE0LSnRLSsu6ps3IRcJg=="); - final byte[] content = Base64.decode("aAAAAAAAAAAAAAAAAAAAALTwrBTNYP7m3yTGKlhka9WPvX1Lpn5EYfVxlyX1ISgRXtdRnivM7r6F3Og="); - - try (FileContentDecryptor decryptor = new FileContentDecryptorImpl(headerKey, macKey, ByteBuffer.wrap(header), 0, true)) { - decryptor.append(ByteBuffer.wrap(Arrays.copyOfRange(content, 0, 15))); - decryptor.append(ByteBuffer.wrap(Arrays.copyOfRange(content, 15, 59))); - decryptor.append(FileContentCryptor.EOF); - - ByteBuffer result = ByteBuffer.allocate(11); // we just care about the first 11 bytes, as this is the ciphertext. - ByteBuffer buf; - while ((buf = decryptor.cleartext()) != FileContentCryptor.EOF) { - ByteBuffers.copy(buf, result); - } - } - } - - @Test - public void testManipulatedDecryptionWithSuppressedAuthentication() throws InterruptedException { - final byte[] keyBytes = new byte[32]; - final SecretKey headerKey = new SecretKeySpec(keyBytes, "AES"); - final SecretKey macKey = new SecretKeySpec(keyBytes, "HmacSHA256"); - final byte[] header = Base64.decode("AAAAAAAAAAAAAAAAAAAAACNqP4ddv3Z2rUiiFJKEIIdTD4r7x0U2ualjtPHEy3OLzqdAPU1ga24VjC86+zlHN49BfMdzvHF3f9EE0LSnRLSsu6ps3IRcJg=="); - final byte[] content = Base64.decode("AAAAAAAAAAAAAAAAAAAAALTwrBTNYP7m3yTGKlhka9WPvX1Lpn5EYfVxlyX1ISgRXtdRnivM7r6F3OG="); - - try (FileContentDecryptor decryptor = new FileContentDecryptorImpl(headerKey, macKey, ByteBuffer.wrap(header), 0, false)) { - decryptor.append(ByteBuffer.wrap(Arrays.copyOfRange(content, 0, 15))); - decryptor.append(ByteBuffer.wrap(Arrays.copyOfRange(content, 15, 59))); - decryptor.append(FileContentCryptor.EOF); - - ByteBuffer result = ByteBuffer.allocate(11); // we just care about the first 11 bytes, as this is the ciphertext. - ByteBuffer buf; - while ((buf = decryptor.cleartext()) != FileContentCryptor.EOF) { - ByteBuffers.copy(buf, result); - } - - Assert.assertArrayEquals("hello world".getBytes(), result.array()); - } - } - - @Test(expected = UncheckedIOException.class) - public void testPassthroughException() throws InterruptedException { - final byte[] keyBytes = new byte[32]; - final SecretKey headerKey = new SecretKeySpec(keyBytes, "AES"); - final SecretKey macKey = new SecretKeySpec(keyBytes, "AES"); - final byte[] header = Base64.decode("AAAAAAAAAAAAAAAAAAAAACNqP4ddv3Z2rUiiFJKEIIdTD4r7x0U2ualjtPHEy3OLzqdAPU1ga24VjC86+zlHN49BfMdzvHF3f9EE0LSnRLSsu6ps3IRcJg=="); - - try (FileContentDecryptor decryptor = new FileContentDecryptorImpl(headerKey, macKey, ByteBuffer.wrap(header), 0, true)) { - decryptor.cancelWithException(new IOException("can not do")); - decryptor.cleartext(); - } - } - - @Test(timeout = 200000) - public void testPartialDecryption() throws InterruptedException { - final byte[] keyBytes = new byte[32]; - final SecretKey encryptionKey = new SecretKeySpec(keyBytes, "AES"); - final SecretKey macKey = new SecretKeySpec(keyBytes, "HmacSHA256"); - FileContentCryptor cryptor = new FileContentCryptorImpl(encryptionKey, macKey, RANDOM_MOCK); - - ByteBuffer header = ByteBuffer.allocate(cryptor.getHeaderSize()); - ByteBuffer ciphertext = ByteBuffer.allocate(131264); // 4 * (16 + 32k + 32) - try (FileContentEncryptor encryptor = cryptor.createFileContentEncryptor(Optional.empty(), 0)) { - final Thread ciphertextWriter = new Thread(() -> { - ByteBuffer buf; - try { - while ((buf = encryptor.ciphertext()) != FileContentCryptor.EOF) { - ByteBuffers.copy(buf, ciphertext); - } - ciphertext.flip(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - }); - ciphertextWriter.start(); - - // write cleartext: - ByteBuffer intBuf = ByteBuffer.allocate(32768); - for (int i = 0; i < 4; i++) { - intBuf.clear(); - intBuf.putInt(i); - intBuf.rewind(); - encryptor.append(intBuf); - } - encryptor.append(FileContentCryptor.EOF); - ciphertextWriter.join(); - header = encryptor.getHeader(); - } - - for (int i = 3; i >= 0; i--) { - final int ciphertextPos = (int) cryptor.toCiphertextPos(i * 32768); - try (FileContentDecryptor decryptor = cryptor.createFileContentDecryptor(header, ciphertextPos, true)) { - final Thread ciphertextReader = new Thread(() -> { - try { - ciphertext.position(ciphertextPos); - decryptor.append(ciphertext); - decryptor.append(FileContentCryptor.EOF); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - }); - ciphertextReader.start(); - - // read cleartext: - ByteBuffer decrypted = decryptor.cleartext(); - Assert.assertEquals(i, decrypted.getInt()); - - ciphertextReader.interrupt(); - } - } - - } - -} diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImplTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImplTest.java deleted file mode 100644 index 7c36c28e4..000000000 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImplTest.java +++ /dev/null @@ -1,84 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto.engine.impl; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.security.SecureRandom; -import java.util.Arrays; - -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import org.bouncycastle.util.encoders.Base64; -import org.cryptomator.crypto.engine.FileContentCryptor; -import org.cryptomator.crypto.engine.FileContentEncryptor; -import org.cryptomator.io.ByteBuffers; -import org.junit.Assert; -import org.junit.Test; - -public class FileContentEncryptorImplTest { - - private static final SecureRandom RANDOM_MOCK = new SecureRandom() { - - @Override - public void nextBytes(byte[] bytes) { - Arrays.fill(bytes, (byte) 0x00); - } - - }; - - @Test - public void testEncryption() throws InterruptedException { - final byte[] keyBytes = new byte[32]; - final SecretKey headerKey = new SecretKeySpec(keyBytes, "AES"); - final SecretKey macKey = new SecretKeySpec(keyBytes, "AES"); - - try (FileContentEncryptor encryptor = new FileContentEncryptorImpl(headerKey, macKey, RANDOM_MOCK, 0)) { - encryptor.append(ByteBuffer.wrap("hello ".getBytes())); - encryptor.append(ByteBuffer.wrap("world".getBytes())); - encryptor.append(FileContentCryptor.EOF); - - ByteBuffer result = ByteBuffer.allocate(59); // 16 bytes iv + 11 bytes ciphertext + 32 bytes mac. - ByteBuffer buf; - while ((buf = encryptor.ciphertext()) != FileContentCryptor.EOF) { - ByteBuffers.copy(buf, result); - } - - // # CIPHERTEXT: - // echo -n "hello world" | openssl enc -aes-256-ctr -K 0000000000000000000000000000000000000000000000000000000000000000 -iv 00000000000000000000000000000000 | base64 - // - // # MAC: - // # 0x00-bytes for IV + blocknumber + nonce: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== - // echo -n "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" | base64 --decode > A; echo -n "tPCsFM1g/ubfJMY=" | base64 --decode >> A; - // cat A | openssl dgst -sha256 -mac HMAC -macopt hexkey:0000000000000000000000000000000000000000000000000000000000000000 -binary | base64 - // - // # FULL CHUNK: - // echo -n "AAAAAAAAAAAAAAAAAAAAAA==" | base64 --decode > B; - // echo -n "tPCsFM1g/ubfJMY=" | base64 --decode >> B; - // echo -n "Klhka9WPvX1Lpn5EYfVxlyX1ISgRXtdRnivM7r6F3Og=" | base64 --decode >> B; - // cat B | base64 - Assert.assertArrayEquals(Base64.decode("AAAAAAAAAAAAAAAAAAAAALTwrBTNYP7m3yTGKlhka9WPvX1Lpn5EYfVxlyX1ISgRXtdRnivM7r6F3Og="), result.array()); - } - } - - @Test(expected = UncheckedIOException.class) - public void testPassthroughException() throws InterruptedException { - final byte[] keyBytes = new byte[32]; - final SecretKey headerKey = new SecretKeySpec(keyBytes, "AES"); - final SecretKey macKey = new SecretKeySpec(keyBytes, "AES"); - - try (FileContentEncryptor encryptor = new FileContentEncryptorImpl(headerKey, macKey, RANDOM_MOCK, 0)) { - encryptor.cancelWithException(new IOException("can not do")); - encryptor.ciphertext(); - } - } - -} diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileHeaderPayloadTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileHeaderPayloadTest.java deleted file mode 100644 index e53164f26..000000000 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileHeaderPayloadTest.java +++ /dev/null @@ -1,58 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 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.crypto.engine.impl; - -import java.nio.ByteBuffer; -import java.security.SecureRandom; -import java.util.Arrays; - -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import org.bouncycastle.util.encoders.Base64; -import org.junit.Assert; -import org.junit.Test; - -public class FileHeaderPayloadTest { - - private static final SecureRandom RANDOM_MOCK = new SecureRandom() { - - private static final long serialVersionUID = 1505563778398085504L; - - @Override - public void nextBytes(byte[] bytes) { - Arrays.fill(bytes, (byte) 0x00); - } - - }; - - @Test - public void testEncryption() { - final byte[] keyBytes = new byte[32]; - final SecretKey headerKey = new SecretKeySpec(keyBytes, "AES"); - final FileHeaderPayload header = new FileHeaderPayload(RANDOM_MOCK); - header.setFilesize(42); - final ByteBuffer encrypted = header.toCiphertextByteBuffer(headerKey, new byte[16]); - - // echo -n "AAAAAAAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" | base64 --decode | openssl enc -aes-256-ctr -K 0000000000000000000000000000000000000000000000000000000000000000 -iv - // 00000000000000000000000000000000 | base64 - Assert.assertArrayEquals(Base64.decode("3JXAeKJAiaOtSKIUkoQgh1MPivvHRTa5qWO08cTLc4vOp0A9TWBrbg=="), Arrays.copyOfRange(encrypted.array(), 0, encrypted.remaining())); - } - - @Test - public void testDecryption() { - final byte[] keyBytes = new byte[32]; - final SecretKey headerKey = new SecretKeySpec(keyBytes, "AES"); - final ByteBuffer ciphertextBuf = ByteBuffer.wrap(Base64.decode("3JXAeKJAiaOtSKIUkoQgh1MPivvHRTa5qWO08cTLc4vOp0A9TWBrbg==")); - final FileHeaderPayload header = FileHeaderPayload.fromCiphertextByteBuffer(ciphertextBuf, headerKey, new byte[16]); - Assert.assertEquals(42, header.getFilesize()); - Assert.assertArrayEquals(new byte[32], header.getContentKey().getEncoded()); - } - -} diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileHeaderTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileHeaderTest.java deleted file mode 100644 index bbe61a522..000000000 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileHeaderTest.java +++ /dev/null @@ -1,98 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 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.crypto.engine.impl; - -import java.nio.ByteBuffer; -import java.security.SecureRandom; -import java.util.Arrays; - -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import org.bouncycastle.util.encoders.Base64; -import org.cryptomator.crypto.engine.AuthenticationFailedException; -import org.junit.Assert; -import org.junit.Test; - -public class FileHeaderTest { - - private static final SecureRandom RANDOM_MOCK = new SecureRandom() { - - private static final long serialVersionUID = 1505563778398085504L; - - @Override - public void nextBytes(byte[] bytes) { - Arrays.fill(bytes, (byte) 0x00); - } - - }; - - @Test - public void testEncryption() { - final byte[] keyBytes = new byte[32]; - final SecretKey headerKey = new SecretKeySpec(keyBytes, "AES"); - final SecretKey macKey = new SecretKeySpec(keyBytes, "HmacSHA256"); - final FileHeader header = new FileHeader(RANDOM_MOCK); - header.getPayload().setFilesize(42); - Assert.assertArrayEquals(new byte[16], header.getIv()); - Assert.assertArrayEquals(new byte[32], header.getPayload().getContentKey().getEncoded()); - final ByteBuffer headerAsByteBuffer = header.toByteBuffer(headerKey, new ThreadLocalMac(macKey, "HmacSHA256")); - - // 16 bytes 0x00 (IV) - // + 48 bytes encrypted payload (see FileHeaderPayloadTest) - // + 32 bytes HMAC of both (openssl dgst -sha256 -mac HMAC -macopt hexkey:0000000000000000000000000000000000000000000000000000000000000000 -binary) - final String expected = "AAAAAAAAAAAAAAAAAAAAANyVwHiiQImjrUiiFJKEIIdTD4r7x0U2ualjtPHEy3OLzqdAPU1ga26lJzstK9RUv1hj5zDC4wC9FgMfoVE1mD0HnuENuYXkJA=="; - Assert.assertArrayEquals(Base64.decode(expected), Arrays.copyOf(headerAsByteBuffer.array(), headerAsByteBuffer.remaining())); - } - - @Test - public void testDecryption() { - final byte[] keyBytes = new byte[32]; - final SecretKey headerKey = new SecretKeySpec(keyBytes, "AES"); - final SecretKey macKey = new SecretKeySpec(keyBytes, "HmacSHA256"); - final ByteBuffer headerBuf = ByteBuffer.wrap(Base64.decode("AAAAAAAAAAAAAAAAAAAAACNqP4ddv3Z2rUiiFJKEIIdTD4r7x0U2ualjtPHEy3OLzqdAPU1ga24VjC86+zlHN49BfMdzvHF3f9EE0LSnRLSsu6ps3IRcJg==")); - final FileHeader header = FileHeader.decrypt(headerKey, new ThreadLocalMac(macKey, "HmacSHA256"), headerBuf); - - Assert.assertEquals(-1l, header.getPayload().getFilesize()); - Assert.assertArrayEquals(new byte[16], header.getIv()); - Assert.assertArrayEquals(new byte[32], header.getPayload().getContentKey().getEncoded()); - } - - @Test - public void testDecryptionOfOldHeader() { - final byte[] keyBytes = new byte[32]; - final SecretKey headerKey = new SecretKeySpec(keyBytes, "AES"); - final SecretKey macKey = new SecretKeySpec(keyBytes, "HmacSHA256"); - final ByteBuffer headerBuf = ByteBuffer.wrap(Base64.decode("AAAAAAAAAAAAAAAAAAAAANyVwHiiQImjrUiiFJKEIIdTD4r7x0U2ualjtPHEy3OLzqdAPU1ga26lJzstK9RUv1hj5zDC4wC9FgMfoVE1mD0HnuENuYXkJA==")); - final FileHeader header = FileHeader.decrypt(headerKey, new ThreadLocalMac(macKey, "HmacSHA256"), headerBuf); - - Assert.assertEquals(42l, header.getPayload().getFilesize()); - Assert.assertArrayEquals(new byte[16], header.getIv()); - Assert.assertArrayEquals(new byte[32], header.getPayload().getContentKey().getEncoded()); - } - - @Test(expected = AuthenticationFailedException.class) - public void testDecryptionWithInvalidMac1() { - final byte[] keyBytes = new byte[32]; - final SecretKey headerKey = new SecretKeySpec(keyBytes, "AES"); - final SecretKey macKey = new SecretKeySpec(keyBytes, "HmacSHA256"); - final ByteBuffer headerBuf = ByteBuffer.wrap(Base64.decode("AAAAAAAAAAAAAAAAAAAAANyVwHiiQImjrUiiFJKEIIdTD4r7x0U2ualjtPHEy3OLzqdAPU1ga26lJzstK9RUv1hj5zDC4wC9FgMfoVE1mD0HnuENuYXkJa==")); - FileHeader.decrypt(headerKey, new ThreadLocalMac(macKey, "HmacSHA256"), headerBuf); - } - - @Test(expected = AuthenticationFailedException.class) - public void testDecryptionWithInvalidMac2() { - final byte[] keyBytes = new byte[32]; - final SecretKey headerKey = new SecretKeySpec(keyBytes, "AES"); - final SecretKey macKey = new SecretKeySpec(keyBytes, "HmacSHA256"); - final ByteBuffer headerBuf = ByteBuffer.wrap(Base64.decode("aAAAAAAAAAAAAAAAAAAAANyVwHiiQImjrUiiFJKEIIdTD4r7x0U2ualjtPHEy3OLzqdAPU1ga26lJzstK9RUv1hj5zDC4wC9FgMfoVE1mD0HnuENuYXkJA==")); - FileHeader.decrypt(headerKey, new ThreadLocalMac(macKey, "HmacSHA256"), headerBuf); - } - -} diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImplTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImplTest.java deleted file mode 100644 index 33f7efad1..000000000 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImplTest.java +++ /dev/null @@ -1,115 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto.engine.impl; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import java.io.IOException; -import java.util.UUID; - -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import org.cryptomator.crypto.engine.AuthenticationFailedException; -import org.cryptomator.crypto.engine.FilenameCryptor; -import org.junit.Assert; -import org.junit.Test; - -public class FilenameCryptorImplTest { - - @Test - public void testDeterministicEncryptionOfFilenames() throws IOException { - final byte[] keyBytes = new byte[32]; - final SecretKey encryptionKey = new SecretKeySpec(keyBytes, "AES"); - final SecretKey macKey = new SecretKeySpec(keyBytes, "AES"); - final FilenameCryptor filenameCryptor = new FilenameCryptorImpl(encryptionKey, macKey); - - // some random - for (int i = 0; i < 2000; i++) { - final String origName = UUID.randomUUID().toString(); - final String encrypted1 = filenameCryptor.encryptFilename(origName); - final String encrypted2 = filenameCryptor.encryptFilename(origName); - Assert.assertEquals(encrypted1, encrypted2); - final String decrypted = filenameCryptor.decryptFilename(encrypted1); - Assert.assertEquals(origName, decrypted); - } - - // block size length file names - final String originalPath3 = "aaaabbbbccccdddd"; // 128 bit ascii - final String encryptedPath3a = filenameCryptor.encryptFilename(originalPath3); - final String encryptedPath3b = filenameCryptor.encryptFilename(originalPath3); - Assert.assertEquals(encryptedPath3a, encryptedPath3b); - final String decryptedPath3 = filenameCryptor.decryptFilename(encryptedPath3a); - Assert.assertEquals(originalPath3, decryptedPath3); - } - - @Test - public void testDeterministicHashingOfDirectoryIds() throws IOException { - final byte[] keyBytes = new byte[32]; - final SecretKey encryptionKey = new SecretKeySpec(keyBytes, "AES"); - final SecretKey macKey = new SecretKeySpec(keyBytes, "AES"); - final FilenameCryptor filenameCryptor = new FilenameCryptorImpl(encryptionKey, macKey); - - // some random - for (int i = 0; i < 2000; i++) { - final String originalDirectoryId = UUID.randomUUID().toString(); - final String hashedDirectory1 = filenameCryptor.hashDirectoryId(originalDirectoryId); - final String hashedDirectory2 = filenameCryptor.hashDirectoryId(originalDirectoryId); - Assert.assertEquals(hashedDirectory1, hashedDirectory2); - } - } - - @Test(expected = AuthenticationFailedException.class) - public void testDecryptionOfManipulatedFilename() { - final byte[] keyBytes = new byte[32]; - final SecretKey encryptionKey = new SecretKeySpec(keyBytes, "AES"); - final SecretKey macKey = new SecretKeySpec(keyBytes, "AES"); - final FilenameCryptor filenameCryptor = new FilenameCryptorImpl(encryptionKey, macKey); - - final byte[] encrypted = filenameCryptor.encryptFilename("test").getBytes(UTF_8); - encrypted[0] ^= (byte) 0x01; // change 1 bit in first byte - filenameCryptor.decryptFilename(new String(encrypted, UTF_8)); - } - - @Test - public void testEncryptionOfSameFilenamesWithDifferentAssociatedData() { - final byte[] keyBytes = new byte[32]; - final SecretKey encryptionKey = new SecretKeySpec(keyBytes, "AES"); - final SecretKey macKey = new SecretKeySpec(keyBytes, "AES"); - final FilenameCryptor filenameCryptor = new FilenameCryptorImpl(encryptionKey, macKey); - - final String encrypted1 = filenameCryptor.encryptFilename("test", "ad1".getBytes(UTF_8)); - final String encrypted2 = filenameCryptor.encryptFilename("test", "ad2".getBytes(UTF_8)); - Assert.assertNotEquals(encrypted1, encrypted2); - } - - @Test - public void testDeterministicEncryptionOfFilenamesWithAssociatedData() { - final byte[] keyBytes = new byte[32]; - final SecretKey encryptionKey = new SecretKeySpec(keyBytes, "AES"); - final SecretKey macKey = new SecretKeySpec(keyBytes, "AES"); - final FilenameCryptor filenameCryptor = new FilenameCryptorImpl(encryptionKey, macKey); - - final String encrypted = filenameCryptor.encryptFilename("test", "ad".getBytes(UTF_8)); - final String decrypted = filenameCryptor.decryptFilename(encrypted, "ad".getBytes(UTF_8)); - Assert.assertEquals("test", decrypted); - } - - @Test(expected = AuthenticationFailedException.class) - public void testDeterministicEncryptionOfFilenamesWithWrongAssociatedData() { - final byte[] keyBytes = new byte[32]; - final SecretKey encryptionKey = new SecretKeySpec(keyBytes, "AES"); - final SecretKey macKey = new SecretKeySpec(keyBytes, "AES"); - final FilenameCryptor filenameCryptor = new FilenameCryptorImpl(encryptionKey, macKey); - - final String encrypted = filenameCryptor.encryptFilename("test", "right".getBytes(UTF_8)); - filenameCryptor.decryptFilename(encrypted, "wrong".getBytes(UTF_8)); - } - -} diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/TestCryptorImplFactory.java b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/TestCryptorImplFactory.java deleted file mode 100644 index 8a4c1a56d..000000000 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/TestCryptorImplFactory.java +++ /dev/null @@ -1,36 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 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.crypto.engine.impl; - -import java.security.SecureRandom; -import java.util.Arrays; - -import org.cryptomator.crypto.engine.Cryptor; - -public class TestCryptorImplFactory { - - private static final SecureRandom RANDOM_MOCK = new SecureRandom() { - - private static final long serialVersionUID = 1505563778398085504L; - - @Override - public void nextBytes(byte[] bytes) { - Arrays.fill(bytes, (byte) 0x00); - } - - }; - - /** - * @return A CryptorImpl with a mocked PRNG, that can be used during tests without the need of "real" random numbers. - */ - public static Cryptor insecureCryptorImpl() { - return new CryptorImpl(RANDOM_MOCK); - } - -} diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/BlockAlignedReadableFileTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/BlockAlignedReadableFileTest.java deleted file mode 100644 index 5dba87c4d..000000000 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/BlockAlignedReadableFileTest.java +++ /dev/null @@ -1,96 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto; - -import java.nio.ByteBuffer; - -import org.bouncycastle.util.Arrays; -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.filesystem.WritableFile; -import org.cryptomator.filesystem.crypto.BlockAlignedReadableFile; -import org.cryptomator.filesystem.inmem.InMemoryFileSystem; -import org.junit.Assert; -import org.junit.Test; -import org.mockito.Mockito; - -public class BlockAlignedReadableFileTest { - - @Test(expected = IllegalArgumentException.class) - public void testInvalidBlockSize() { - @SuppressWarnings(value = {"resource", "unused"}) - ReadableFile r = new BlockAlignedReadableFile(null, 0); - } - - @Test - public void testSwitchingModes() { - FileSystem fs = new InMemoryFileSystem(); - File file = fs.file("test"); - try (WritableFile w = file.openWritable()) { - w.write(ByteBuffer.wrap(new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09})); - } - - BlockAlignedReadableFile readable = Mockito.spy(new BlockAlignedReadableFile(file.openReadable(), 2)); - ByteBuffer firstRead = ByteBuffer.allocate(4); - readable.read(firstRead); - Mockito.verify(readable, Mockito.never()).switchToBlockAlignedMode(); - readable.position(0); - Mockito.verify(readable).switchToBlockAlignedMode(); - ByteBuffer secondRead = ByteBuffer.allocate(4); - readable.read(secondRead); - Assert.assertArrayEquals(firstRead.array(), secondRead.array()); - readable.close(); - } - - @Test - public void testRead() { - FileSystem fs = new InMemoryFileSystem(); - File file = fs.file("test"); - try (WritableFile w = file.openWritable()) { - w.write(ByteBuffer.wrap(new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09})); - } - - for (int i = 1; i < 12; i++) { - testRead(file, i); - } - } - - private void testRead(File file, int blockSize) { - try (ReadableFile r = new BlockAlignedReadableFile(file.openReadable(), blockSize)) { - ByteBuffer buf = ByteBuffer.allocate(3); - - // 3... - r.position(3); - r.read(buf); - buf.flip(); - Assert.assertArrayEquals(new byte[] {0x03, 0x04, 0x05}, Arrays.copyOf(buf.array(), buf.remaining())); - - // go on... - buf.clear(); - r.read(buf); - buf.flip(); - Assert.assertArrayEquals(new byte[] {0x06, 0x07, 0x08}, Arrays.copyOf(buf.array(), buf.remaining())); - - // go on till EOF... - buf.clear(); - r.read(buf); - buf.flip(); - Assert.assertArrayEquals(new byte[] {0x09}, Arrays.copyOf(buf.array(), buf.remaining())); - - // back to 4... - r.position(4); - buf.clear(); - r.read(buf); - buf.flip(); - Assert.assertArrayEquals(new byte[] {0x04, 0x05, 0x06}, Arrays.copyOf(buf.array(), buf.remaining())); - } - } - -} diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/BlockAlignedWritableFileTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/BlockAlignedWritableFileTest.java deleted file mode 100644 index 66e70eab0..000000000 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/BlockAlignedWritableFileTest.java +++ /dev/null @@ -1,76 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto; - -import java.nio.ByteBuffer; - -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.filesystem.WritableFile; -import org.cryptomator.filesystem.crypto.BlockAlignedWritableFile; -import org.cryptomator.filesystem.inmem.InMemoryFileSystem; -import org.junit.Assert; -import org.junit.Test; -import org.mockito.Mockito; - -public class BlockAlignedWritableFileTest { - - @Test - public void testSwitchingModes() { - FileSystem fs = new InMemoryFileSystem(); - File file = fs.file("test"); - try (WritableFile w = file.openWritable()) { - w.write(ByteBuffer.wrap(new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09})); - } - - BlockAlignedWritableFile writable = Mockito.spy(new BlockAlignedWritableFile(file::openWritable, file::openReadable, 2)); - writable.write(ByteBuffer.wrap(new byte[] {0x11, 0x12, 0x13})); - Mockito.verify(writable, Mockito.never()).switchToBlockAlignedMode(); - writable.position(1); - Mockito.verify(writable).switchToBlockAlignedMode(); - writable.write(ByteBuffer.wrap(new byte[] {0x14, 0x15, 0x16})); - writable.close(); - - try (ReadableFile r = file.openReadable()) { - ByteBuffer buf = ByteBuffer.allocate(10); - r.read(buf); - buf.flip(); - Assert.assertArrayEquals(new byte[] {0x11, 0x14, 0x15, 0x16, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09}, buf.array()); - } - } - - @Test - public void testWrite() { - FileSystem fs = new InMemoryFileSystem(); - File file = fs.file("test"); - try (WritableFile w = file.openWritable()) { - w.write(ByteBuffer.wrap(new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09})); - } - - for (int i = 1; i < 12; i++) { - testWrite(file, i); - } - } - - private void testWrite(File file, int blockSize) { - try (WritableFile w = new BlockAlignedWritableFile(file::openWritable, file::openReadable, blockSize)) { - w.position(4); - w.write(ByteBuffer.wrap(new byte[] {0x11, 0x22, 0x33})); - } - - try (ReadableFile r = file.openReadable()) { - ByteBuffer buf = ByteBuffer.allocate(10); - r.read(buf); - buf.flip(); - Assert.assertArrayEquals(new byte[] {0x00, 0x01, 0x02, 0x03, 0x11, 0x22, 0x33, 0x07, 0x08, 0x09}, buf.array()); - } - } - -} diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/ConflictResolverTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/ConflictResolverTest.java deleted file mode 100644 index 0e7d7f75a..000000000 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/ConflictResolverTest.java +++ /dev/null @@ -1,161 +0,0 @@ -package org.cryptomator.filesystem.crypto; - -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.Optional; -import java.util.function.Function; -import java.util.regex.Pattern; - -import org.apache.commons.codec.binary.Base32; -import org.apache.commons.codec.binary.BaseNCodec; -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.ReadableFile; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -public class ConflictResolverTest { - - private ConflictResolver conflictResolver; - private Folder folder; - private File canonicalFile; - private File canonicalFolder; - private File conflictingFile; - private File conflictingFolder; - private File resolved; - private File unrelatedFile; - - @Before - public void setup() { - Pattern base32Pattern = Pattern.compile("([A-Z0-9]{8})*[A-Z0-9=]{8}"); - BaseNCodec base32 = new Base32(); - Function> decode = (s) -> Optional.of(new String(base32.decode(s), StandardCharsets.UTF_8)); - Function> encode = (s) -> Optional.of(base32.encodeAsString(s.getBytes(StandardCharsets.UTF_8))); - conflictResolver = new ConflictResolver(base32Pattern, decode, encode); - - folder = Mockito.mock(Folder.class); - canonicalFile = Mockito.mock(File.class); - canonicalFolder = Mockito.mock(File.class); - conflictingFile = Mockito.mock(File.class); - conflictingFolder = Mockito.mock(File.class); - resolved = Mockito.mock(File.class); - unrelatedFile = Mockito.mock(File.class); - - String canonicalFileName = encode.apply("test name").get(); - String canonicalFolderName = Constants.DIR_PREFIX + canonicalFileName; - String conflictingFileName = canonicalFileName + " (version 2)"; - String conflictingFolderName = canonicalFolderName + " (version 2)"; - String unrelatedName = "notBa$e32!"; - - Mockito.when(canonicalFile.name()).thenReturn(canonicalFileName); - Mockito.when(canonicalFolder.name()).thenReturn(canonicalFolderName); - Mockito.when(conflictingFile.name()).thenReturn(conflictingFileName); - Mockito.when(conflictingFolder.name()).thenReturn(conflictingFolderName); - Mockito.when(unrelatedFile.name()).thenReturn(unrelatedName); - - Mockito.when(canonicalFile.exists()).thenReturn(true); - Mockito.when(canonicalFolder.exists()).thenReturn(true); - Mockito.when(conflictingFile.exists()).thenReturn(true); - Mockito.when(conflictingFolder.exists()).thenReturn(true); - Mockito.when(unrelatedFile.exists()).thenReturn(true); - - Mockito.doReturn(Optional.of(folder)).when(canonicalFile).parent(); - Mockito.doReturn(Optional.of(folder)).when(canonicalFolder).parent(); - Mockito.doReturn(Optional.of(folder)).when(conflictingFile).parent(); - Mockito.doReturn(Optional.of(folder)).when(conflictingFolder).parent(); - Mockito.doReturn(Optional.of(folder)).when(unrelatedFile).parent(); - - Mockito.when(folder.file(Mockito.startsWith(canonicalFileName.substring(0, 8)))).thenReturn(resolved); - Mockito.when(folder.file(Mockito.startsWith(canonicalFolderName.substring(0, 8)))).thenReturn(resolved); - Mockito.when(folder.file(canonicalFileName)).thenReturn(canonicalFile); - Mockito.when(folder.file(canonicalFolderName)).thenReturn(canonicalFolder); - Mockito.when(folder.file(conflictingFileName)).thenReturn(conflictingFile); - Mockito.when(folder.file(conflictingFolderName)).thenReturn(conflictingFolder); - Mockito.when(folder.file(unrelatedName)).thenReturn(unrelatedFile); - } - - @Test - public void testCanonicalName() { - File result = conflictResolver.resolveIfNecessary(canonicalFile); - Assert.assertSame(canonicalFile, result); - } - - @Test - public void testUnrelatedName() { - File result = conflictResolver.resolveIfNecessary(unrelatedFile); - Assert.assertSame(unrelatedFile, result); - } - - @Test - public void testConflictingFile() { - File result = conflictResolver.resolveIfNecessary(conflictingFile); - Mockito.verify(conflictingFile).moveTo(resolved); - Assert.assertSame(resolved, result); - } - - @Test - public void testConflictingFileIfCanonicalDoesntExist() { - Mockito.when(canonicalFile.exists()).thenReturn(false); - File result = conflictResolver.resolveIfNecessary(conflictingFile); - Mockito.verify(conflictingFile).moveTo(resolved); - Assert.assertSame(resolved, result); - } - - @Test - public void testConflictingFolderWithDifferentId() { - ReadableFile directoryId1 = Mockito.mock(ReadableFile.class); - ReadableFile directoryId2 = Mockito.mock(ReadableFile.class); - Mockito.when(canonicalFolder.openReadable()).thenReturn(directoryId1); - Mockito.when(conflictingFolder.openReadable()).thenReturn(directoryId2); - Mockito.when(directoryId1.read(Mockito.any())).thenAnswer(new FillBufferAnswer("id1")); - Mockito.when(directoryId2.read(Mockito.any())).thenAnswer(new FillBufferAnswer("id2")); - - File result = conflictResolver.resolveIfNecessary(conflictingFolder); - Mockito.verify(conflictingFolder).moveTo(resolved); - Assert.assertSame(resolved, result); - } - - @Test - public void testConflictingFolderWithSameId() { - ReadableFile directoryId1 = Mockito.mock(ReadableFile.class); - ReadableFile directoryId2 = Mockito.mock(ReadableFile.class); - Mockito.when(canonicalFolder.openReadable()).thenReturn(directoryId1); - Mockito.when(conflictingFolder.openReadable()).thenReturn(directoryId2); - Mockito.when(directoryId1.read(Mockito.any())).thenAnswer(new FillBufferAnswer("id1")); - Mockito.when(directoryId2.read(Mockito.any())).thenAnswer(new FillBufferAnswer("id1")); - - File result = conflictResolver.resolveIfNecessary(conflictingFolder); - Mockito.verify(conflictingFolder).delete(); - Assert.assertSame(canonicalFolder, result); - } - - private static class FillBufferAnswer implements Answer { - - private final byte[] content; - private int bytesRead = 0; - - public FillBufferAnswer(String content) { - this.content = content.getBytes(StandardCharsets.UTF_8); - } - - @Override - public Integer answer(InvocationOnMock invocation) throws Throwable { - if (bytesRead >= content.length) { - bytesRead = 0; - return -1; - } else { - ByteBuffer buf = invocation.getArgumentAt(0, ByteBuffer.class); - int delta = Math.min(content.length - bytesRead, buf.remaining()); - buf.put(content, bytesRead, delta); - bytesRead += delta; - return content.length; - } - } - - } - -} diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/CryptoFileSystemTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/CryptoFileSystemTest.java deleted file mode 100644 index a8dd8fec3..000000000 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/CryptoFileSystemTest.java +++ /dev/null @@ -1,229 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.crypto; - -import static org.hamcrest.Matchers.both; -import static org.hamcrest.Matchers.greaterThanOrEqualTo; -import static org.hamcrest.Matchers.lessThanOrEqualTo; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.time.Instant; -import java.util.Arrays; - -import org.cryptomator.crypto.engine.Cryptor; -import org.cryptomator.crypto.engine.NoCryptor; -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.filesystem.WritableFile; -import org.cryptomator.filesystem.inmem.InMemoryFileSystem; -import org.junit.Assert; -import org.junit.Test; -import org.mockito.Mockito; - -public class CryptoFileSystemTest { - - @Test(timeout = 1000) - public void testDirectoryCreation() throws UncheckedIOException, IOException { - // mock stuff and prepare crypto FS: - final Cryptor cryptor = new NoCryptor(); - final FileSystem physicalFs = new InMemoryFileSystem(); - final Folder physicalDataRoot = physicalFs.folder("d"); - final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor, Mockito.mock(CryptoFileSystemDelegate.class), "foo"); - - // add another encrypted folder: - final Folder fooFolder = fs.folder("foo"); - final Folder fooBarFolder = fooFolder.folder("bar"); - Assert.assertFalse(fooFolder.exists()); - Assert.assertFalse(fooBarFolder.exists()); - fooBarFolder.create(); - Assert.assertTrue(fooFolder.exists()); - Assert.assertTrue(fooBarFolder.exists()); - Assert.assertEquals(3, countDataFolders(physicalDataRoot)); // parent + foo + bar - } - - @Test(timeout = 1000) - public void testDirectoryDeletion() throws UncheckedIOException, IOException { - // mock stuff and prepare crypto FS: - final Cryptor cryptor = new NoCryptor(); - final FileSystem physicalFs = new InMemoryFileSystem(); - final Folder physicalDataRoot = physicalFs.folder("d"); - final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor, Mockito.mock(CryptoFileSystemDelegate.class), "foo"); - - // create and delete folders: - fs.folder("foo").folder("bar").folder("baz").create(); - Assert.assertEquals(4, countDataFolders(physicalDataRoot)); // root + foo + bar + baz - Assert.assertThat(physicalDataRoot.folders().count(), both(greaterThanOrEqualTo(1l)).and(lessThanOrEqualTo(4l))); // parent folders of the 4 folders - fs.folder("foo").delete(); - Assert.assertEquals(1, countDataFolders(physicalDataRoot)); // just root - Assert.assertEquals(1, physicalDataRoot.folders().count()); // just the parent of root - } - - @Test(timeout = 2000) - public void testDirectoryCopyAndMove() throws UncheckedIOException, IOException { - // mock stuff and prepare crypto FS: - final Cryptor cryptor = new NoCryptor(); - final FileSystem physicalFs = new InMemoryFileSystem(); - final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor, Mockito.mock(CryptoFileSystemDelegate.class), "foo"); - - // create /src/one/two/ and /dst/one: - final Folder src = fs.folder("src"); - final Folder srcSub = src.folder("one"); - final Folder srcSubSub = srcSub.folder("two"); - final Folder dst = fs.folder("dst"); - final Folder dstSub = dst.folder("one"); - final Folder dstSubSub = dstSub.folder("two"); - final Folder dst2 = fs.folder("dst2"); - - srcSubSub.create(); - dstSub.create(); - Assert.assertTrue(srcSubSub.exists()); - Assert.assertTrue(dstSub.exists()); - Assert.assertFalse(dstSubSub.exists()); - Assert.assertFalse(dst2.exists()); - - src.copyTo(dst2); - Assert.assertTrue(dst2.exists()); - Assert.assertTrue(dst2.folder("one").exists()); - Assert.assertTrue(dst2.folder("one").folder("two").exists()); - - dst.delete(); - Assert.assertFalse(dst.exists()); - Assert.assertFalse(dst.folder("one").exists()); - Assert.assertFalse(dst.folder("one").folder("two").exists()); - - dst2.moveTo(dst); - Assert.assertTrue(dst.exists()); - Assert.assertTrue(dst.folder("one").exists()); - Assert.assertTrue(dst.folder("one").folder("two").exists()); - - dst.folder("one").delete(); - Assert.assertTrue(dst.exists()); - Assert.assertFalse(dst.folder("one").exists()); - Assert.assertFalse(dst.folder("one").folder("two").exists()); - - dst.copyTo(dst2); - Assert.assertTrue(dst2.exists()); - Assert.assertFalse(dst2.folder("one").exists()); - Assert.assertFalse(dst2.folder("one").folder("two").exists()); - } - - @Test(timeout = 1000) - public void testDirectoryMoving() throws UncheckedIOException, IOException { - // mock stuff and prepare crypto FS: - final Cryptor cryptor = new NoCryptor(); - final FileSystem physicalFs = new InMemoryFileSystem(); - final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor, Mockito.mock(CryptoFileSystemDelegate.class), "foo"); - fs.create(); - - // create foo/bar/ and then move foo/ to baz/: - final Folder fooFolder = fs.folder("foo"); - final Folder fooBarFolder = fooFolder.folder("bar"); - final Folder bazFolder = fs.folder("baz"); - final Folder bazBarFolder = bazFolder.folder("bar"); - fooBarFolder.create(); - Assert.assertTrue(fooBarFolder.exists()); - Assert.assertFalse(bazFolder.exists()); - fooFolder.moveTo(bazFolder); - // foo/bar/ should no longer exist, but baz/bar/ should: - Assert.assertFalse(fooBarFolder.exists()); - Assert.assertTrue(bazFolder.exists()); - Assert.assertTrue(bazBarFolder.exists()); - } - - @Test(timeout = 1000, expected = UnsupportedOperationException.class) - public void testMovingOfRootDir() throws UncheckedIOException, IOException { - // mock stuff and prepare crypto FS: - final Cryptor cryptor = new NoCryptor(); - final FileSystem physicalFs = new InMemoryFileSystem(); - final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor, Mockito.mock(CryptoFileSystemDelegate.class), "foo"); - fs.create(); - fs.moveTo(fs.folder("subFolder")); - } - - @Test(timeout = 1000, expected = UnsupportedOperationException.class) - public void testDeletingOfRootDir() throws UncheckedIOException, IOException { - // mock stuff and prepare crypto FS: - final Cryptor cryptor = new NoCryptor(); - final FileSystem physicalFs = new InMemoryFileSystem(); - final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor, Mockito.mock(CryptoFileSystemDelegate.class), "foo"); - fs.create(); - fs.delete(); - } - - @Test(timeout = 100000) - public void testCreationAndLastModifiedDateOfRootDir() throws UncheckedIOException, IOException, InterruptedException { - // mock stuff and prepare crypto FS: - final Cryptor cryptor = new NoCryptor(); - final FileSystem physicalFs = new InMemoryFileSystem(); - - final Instant minDate = Instant.now(); - Thread.sleep(10); - final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor, Mockito.mock(CryptoFileSystemDelegate.class), "foo"); - Thread.sleep(10); - final Instant maxDate = Instant.now(); - - Assert.assertTrue(fs.creationTime().isPresent()); - Assert.assertTrue(fs.creationTime().get().isAfter(minDate)); - Assert.assertTrue(fs.creationTime().get().isBefore(maxDate)); - Assert.assertTrue(fs.lastModified().isAfter(minDate)); - Assert.assertTrue(fs.lastModified().isBefore(maxDate)); - } - - @Test(timeout = 1000, expected = IllegalArgumentException.class) - public void testDirectoryMovingWithinBloodline() throws UncheckedIOException, IOException { - // mock stuff and prepare crypto FS: - final Cryptor cryptor = new NoCryptor(); - final FileSystem physicalFs = new InMemoryFileSystem(); - final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor, Mockito.mock(CryptoFileSystemDelegate.class), "foo"); - fs.create(); - - // create foo/bar/ and then try to move foo/bar/ to foo/ - final Folder fooFolder = fs.folder("foo"); - final Folder fooBarFolder = fooFolder.folder("bar"); - fooBarFolder.create(); - fooBarFolder.moveTo(fooFolder); - } - - @Test(timeout = 10000) - public void testWriteAndReadEncryptedFile() { - // mock stuff and prepare crypto FS: - final Cryptor cryptor = new NoCryptor(); - final FileSystem physicalFs = new InMemoryFileSystem(); - final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor, Mockito.mock(CryptoFileSystemDelegate.class), "foo"); - fs.create(); - - // write test content to file - try (WritableFile writable = fs.file("test1.txt").openWritable()) { - writable.write(ByteBuffer.wrap("Hello World".getBytes())); - } - - // read test content from file - try (ReadableFile readable = fs.file("test1.txt").openReadable()) { - ByteBuffer buf1 = ByteBuffer.allocate(5); - readable.read(buf1); - buf1.flip(); - Assert.assertEquals("Hello", new String(buf1.array(), 0, buf1.remaining())); - ByteBuffer buf2 = ByteBuffer.allocate(10); - readable.read(buf2); - buf2.flip(); - Assert.assertArrayEquals(" World".getBytes(), Arrays.copyOfRange(buf2.array(), 0, buf2.remaining())); - } - } - - /** - * @return number of folders on second level inside the given dataRoot folder. - */ - private static int countDataFolders(Folder dataRoot) { - return (int) dataRoot.folders().flatMap(Folder::folders).count(); - } - -} diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/CryptoReadableFileTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/CryptoReadableFileTest.java deleted file mode 100644 index a137f1145..000000000 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/CryptoReadableFileTest.java +++ /dev/null @@ -1,48 +0,0 @@ -/******************************************************************************* - * 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.crypto; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; - -import org.cryptomator.crypto.engine.FileContentCryptor; -import org.cryptomator.crypto.engine.NoCryptor; -import org.cryptomator.filesystem.ReadableFile; -import org.junit.Test; -import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -public class CryptoReadableFileTest { - - @Test(expected = UncheckedIOException.class) - public void testPassthroughExceptions() { - FileContentCryptor fileContentCryptor = new NoCryptor().getFileContentCryptor(); - - // return a valid header but throw exception on consecutive read attempts: - ReadableFile underlyingFile = Mockito.mock(ReadableFile.class); - Mockito.when(underlyingFile.read(Mockito.any(ByteBuffer.class))).thenAnswer(new Answer() { - @Override - public Integer answer(InvocationOnMock invocation) throws Throwable { - ByteBuffer buf = (ByteBuffer) invocation.getArguments()[0]; - buf.position(fileContentCryptor.getHeaderSize()); - return fileContentCryptor.getHeaderSize(); - } - }).thenThrow(new UncheckedIOException(new IOException("failed."))); - - Runnable noop = () -> { - }; - - @SuppressWarnings("resource") - ReadableFile cryptoReadableFile = new CryptoReadableFile(fileContentCryptor, underlyingFile, true, noop); - cryptoReadableFile.read(ByteBuffer.allocate(1)); - } - -} diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/MasterkeysTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/MasterkeysTest.java deleted file mode 100644 index 56ce761fc..000000000 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/MasterkeysTest.java +++ /dev/null @@ -1,70 +0,0 @@ -/******************************************************************************* - * 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.crypto; - -import java.nio.ByteBuffer; - -import org.cryptomator.crypto.engine.InvalidPassphraseException; -import org.cryptomator.crypto.engine.impl.TestCryptorImplFactory; -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.WritableFile; -import org.cryptomator.filesystem.inmem.InMemoryFileSystem; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -public class MasterkeysTest { - - private FileSystem fs; - private Masterkeys m; - - @Before - public void setup() { - fs = new InMemoryFileSystem(); - m = new Masterkeys(TestCryptorImplFactory::insecureCryptorImpl); - } - - @Test - public void testInitialize() { - m.initialize(fs, "asd"); - Assert.assertTrue(fs.file("masterkey.cryptomator").exists()); - } - - @Test - public void testBackup() { - try (WritableFile w = fs.file("masterkey.cryptomator").openWritable()) { - w.write(ByteBuffer.wrap("asd".getBytes())); - } - m.backup(fs); - Assert.assertTrue(fs.file("masterkey.cryptomator.bkup").exists()); - } - - @Test - public void testRestoreBackup() { - try (WritableFile w = fs.file("masterkey.cryptomator.bkup").openWritable()) { - w.write(ByteBuffer.wrap("asd".getBytes())); - } - m.restoreBackup(fs); - Assert.assertTrue(fs.file("masterkey.cryptomator").exists()); - } - - @Test - public void testChangePassphraseWithCorrectPassword() { - m.initialize(fs, "foo"); - m.changePassphrase(fs, "foo", "bar"); - Assert.assertNotNull(m.decrypt(fs, "bar")); - } - - @Test(expected = InvalidPassphraseException.class) - public void testChangePassphraseWithIncorrectPassword() { - m.initialize(fs, "foo"); - m.changePassphrase(fs, "wrong", "bar"); - } - -} diff --git a/main/filesystem-crypto/src/test/resources/log4j2.xml b/main/filesystem-crypto/src/test/resources/log4j2.xml deleted file mode 100644 index 9b4889392..000000000 --- a/main/filesystem-crypto/src/test/resources/log4j2.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/main/filesystem-inmemory/.gitignore b/main/filesystem-inmemory/.gitignore deleted file mode 100644 index b83d22266..000000000 --- a/main/filesystem-inmemory/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target/ diff --git a/main/filesystem-inmemory/pom.xml b/main/filesystem-inmemory/pom.xml deleted file mode 100644 index 18fe0427e..000000000 --- a/main/filesystem-inmemory/pom.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - 4.0.0 - - org.cryptomator - main - 1.3.0-SNAPSHOT - - filesystem-inmemory - Cryptomator filesystem: In-memory mock - - - - org.cryptomator - filesystem-api - - - - - org.cryptomator - commons-test - - - - - - - org.jacoco - jacoco-maven-plugin - - - - \ No newline at end of file diff --git a/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFile.java b/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFile.java deleted file mode 100644 index 781b079b1..000000000 --- a/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFile.java +++ /dev/null @@ -1,141 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.inmem; - -import java.io.FileNotFoundException; -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.nio.file.FileAlreadyExistsException; -import java.time.Instant; -import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; - -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.filesystem.WritableFile; - -class InMemoryFile extends InMemoryNode implements File { - - /** 1000kb */ - static final int INITIAL_SIZE = 100 * 1024; - - /** 140% */ - static final double GROWTH_RATE = 1.4; - - private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); - private final AtomicReference content = new AtomicReference<>(createNewEmptyByteBuffer()); - - public InMemoryFile(InMemoryFolder parent, String name, Instant lastModified, Instant creationTime) { - super(parent, name, lastModified, creationTime); - } - - static ByteBuffer createNewEmptyByteBuffer() { - final ByteBuffer buf = ByteBuffer.allocate(INITIAL_SIZE); - buf.flip(); - return buf; - } - - @Override - public long size() throws UncheckedIOException { - return content.get().limit(); - } - - @Override - public void moveTo(File destination) throws UncheckedIOException { - if (destination instanceof InMemoryFile) { - internalMoveTo((InMemoryFile) destination); - } else { - throw new IllegalArgumentException("Can only move an InMemoryFile to another InMemoryFile"); - } - } - - private void internalMoveTo(InMemoryFile destination) { - this.content.get().rewind(); - destination.create(); - destination.content.set(this.content.getAndSet(createNewEmptyByteBuffer())); - this.delete(); - } - - @Override - public ReadableFile openReadable() { - if (!exists()) { - throw new UncheckedIOException(new FileNotFoundException(this.name() + " does not exist")); - } - boolean success = false; - final ReadLock readLock = lock.readLock(); - readLock.lock(); - try { - final ReadableFile result = new InMemoryReadableFile(content::get, readLock); - success = true; - return result; - } finally { - if (!success) { - readLock.unlock(); - } - } - } - - @Override - public WritableFile openWritable() { - boolean success = false; - final WriteLock writeLock = lock.writeLock(); - writeLock.lock(); - try { - create(); - final WritableFile result = new InMemoryWritableFile(content::get, content::set, writeLock); - success = true; - return result; - } finally { - if (!success) { - writeLock.unlock(); - } - } - } - - private void create() { - final InMemoryFolder parent = parent().get(); - parent.existingChildren.compute(this.name(), (k, v) -> { - if (v != null && v != this) { - // other file or folder with same name already exists. - throw new UncheckedIOException(new FileAlreadyExistsException(k)); - } else { - if (v == null) { - assert !content.get().hasRemaining(); - this.creationTime = Instant.now(); - } - this.lastModified = Instant.now(); - return this; - } - }); - } - - @Override - public void delete() { - content.set(createNewEmptyByteBuffer()); - final InMemoryFolder parent = parent().get(); - parent.existingChildren.computeIfPresent(this.name(), (k, v) -> { - // returning null removes the entry. - return null; - }); - assert !this.exists(); - } - - @Override - public String toString() { - return parent.toString() + name; - } - - @Override - public int compareTo(File o) { - return toString().compareTo(o.toString()); - } - -} diff --git a/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFileSystem.java b/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFileSystem.java deleted file mode 100644 index 492d834c2..000000000 --- a/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFileSystem.java +++ /dev/null @@ -1,54 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.inmem; - -import java.time.Instant; -import java.util.Optional; - -import org.cryptomator.filesystem.FileSystem; - -public class InMemoryFileSystem extends InMemoryFolder implements FileSystem { - - public InMemoryFileSystem() { - super(null, "", Instant.now(), Instant.now()); - } - - @Override - public Optional parent() { - return Optional.empty(); - } - - @Override - public boolean exists() { - return true; - } - - @Override - public void delete() { - // no-op. - } - - @Override - public String toString() { - return "/"; - } - - @Override - public Optional quotaUsedBytes() { - long used = Runtime.getRuntime().totalMemory(); - return Optional.of(used); - } - - @Override - public Optional quotaAvailableBytes() { - long available = Runtime.getRuntime().freeMemory(); - return Optional.of(available); - } - -} diff --git a/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFolder.java b/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFolder.java deleted file mode 100644 index 6d76b8f6b..000000000 --- a/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFolder.java +++ /dev/null @@ -1,121 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.inmem; - -import static java.lang.String.format; - -import java.io.FileNotFoundException; -import java.io.UncheckedIOException; -import java.nio.file.FileAlreadyExistsException; -import java.time.Instant; -import java.util.Iterator; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Stream; - -import org.cryptomator.common.WeakValuedCache; -import org.cryptomator.filesystem.Folder; - -class InMemoryFolder extends InMemoryNode implements Folder { - - final Map existingChildren = new ConcurrentHashMap<>(); - - private final WeakValuedCache folders = WeakValuedCache.usingLoader(this::newFolder); - private final WeakValuedCache files = WeakValuedCache.usingLoader(this::newFile); - - public InMemoryFolder(InMemoryFolder parent, String name, Instant lastModified, Instant creationTime) { - super(parent, name, lastModified, creationTime); - } - - @Override - public Stream children() { - if (exists()) { - return existingChildren.values().stream(); - } else { - throw new UncheckedIOException(new FileNotFoundException(format("Folder %s does not exist", this))); - } - } - - @Override - public InMemoryFile file(String name) { - return files.get(name); - } - - private InMemoryFile newFile(String name) { - return new InMemoryFile(this, name, Instant.MIN, Instant.MIN); - } - - @Override - public InMemoryFolder folder(String name) { - return folders.get(name); - } - - private InMemoryFolder newFolder(String name) { - return new InMemoryFolder(this, name, Instant.MIN, Instant.MIN); - } - - @Override - public void create() { - if (exists()) { - return; - } - parent.create(); - parent.existingChildren.compute(name, (k, v) -> { - if (v != null) { - // other file or folder with same name already exists. - throw new UncheckedIOException(new FileAlreadyExistsException(k)); - } else { - this.lastModified = Instant.now(); - return this; - } - }); - assert this.exists(); - creationTime = Instant.now(); - } - - @Override - public void moveTo(Folder target) { - if (target.exists()) { - target.delete(); - } - assert!target.exists(); - target.create(); - this.copyTo(target); - this.delete(); - assert!this.exists(); - } - - @Override - public void delete() { - // remove ourself from parent: - parent.existingChildren.computeIfPresent(name, (k, v) -> { - // returning null removes the entry. - return null; - }); - // delete all children: - for (Iterator> iterator = existingChildren.entrySet().iterator(); iterator.hasNext();) { - Map.Entry entry = iterator.next(); - iterator.remove(); - // recursively on folders: - if (entry.getValue() instanceof InMemoryFolder) { - InMemoryFolder subFolder = (InMemoryFolder) entry.getValue(); - // this will try to itself from our children, which is ok as - // we're using an iterator here. - subFolder.delete(); - } - } - assert!this.exists(); - } - - @Override - public String toString() { - return parent.toString() + name + "/"; - } - -} diff --git a/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryNode.java b/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryNode.java deleted file mode 100644 index 032fb1ac0..000000000 --- a/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryNode.java +++ /dev/null @@ -1,96 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.inmem; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.time.Instant; -import java.util.Optional; - -import org.cryptomator.filesystem.Node; - -abstract class InMemoryNode implements Node { - - protected final InMemoryFolder parent; - protected final String name; - protected Instant lastModified; - protected Instant creationTime; - - public InMemoryNode(InMemoryFolder parent, String name, Instant lastModified, Instant creationTime) { - this.parent = parent; - this.name = name; - this.lastModified = lastModified; - this.creationTime = creationTime; - } - - @Override - public String name() { - return name; - } - - @Override - public Optional parent() { - return Optional.of(parent); - } - - @Override - public boolean exists() { - return parent.exists() && parent.children().anyMatch(node -> node.equals(this)); - } - - @Override - public Instant lastModified() { - if (!exists()) { - throw new UncheckedIOException(new FileNotFoundException("File does not exist")); - } - return lastModified; - } - - @Override - public void setLastModified(Instant lastModified) throws UncheckedIOException { - this.lastModified = lastModified; - } - - @Override - public Optional creationTime() throws UncheckedIOException { - if (exists()) { - return Optional.of(creationTime); - } else { - throw new UncheckedIOException(new IOException("Node does not exist")); - } - } - - @Override - public void setCreationTime(Instant creationTime) throws UncheckedIOException { - this.creationTime = creationTime; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((name == null) ? 0 : name.hashCode()); - result = prime * result + ((parent == null) ? 0 : parent.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof InMemoryNode) { - InMemoryNode other = (InMemoryNode) obj; - return this.getClass() == other.getClass() // - && (this.parent == null && other.parent == null || this.parent != null && this.parent.equals(other.parent)) // - && (this.name == null && other.name == null || this.name != null && this.name.equals(other.name)); - } else { - return false; - } - } - -} diff --git a/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryReadableFile.java b/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryReadableFile.java deleted file mode 100644 index 8d2212596..000000000 --- a/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryReadableFile.java +++ /dev/null @@ -1,68 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.inmem; - -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; -import java.util.function.Supplier; - -import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.io.ByteBuffers; - -class InMemoryReadableFile implements ReadableFile { - - private final Supplier contentGetter; - private final ReadLock readLock; - private final AtomicInteger position = new AtomicInteger(); - private final AtomicBoolean open = new AtomicBoolean(true); - - public InMemoryReadableFile(Supplier contentGetter, ReadLock readLock) { - this.contentGetter = contentGetter; - this.readLock = readLock; - } - - @Override - public boolean isOpen() { - return open.get(); - } - - @Override - public int read(ByteBuffer destination) throws UncheckedIOException { - ByteBuffer source = contentGetter.get().asReadOnlyBuffer(); - int toBeCopied = destination.remaining(); - int pos = position.getAndAdd(toBeCopied); - if (pos >= source.limit()) { - return -1; - } else { - source.position(pos); - assert source.hasRemaining(); - int numRead = ByteBuffers.copy(source, destination); - assert numRead <= toBeCopied; - return numRead; - } - } - - @Override - public void position(long position) throws UncheckedIOException { - assert position < Integer.MAX_VALUE : "Can not use that big in-memory files."; - this.position.set((int) position); - } - - @Override - public void close() throws UncheckedIOException { - if (open.get()) { - open.set(false); - readLock.unlock(); - } - } - -} diff --git a/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryWritableFile.java b/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryWritableFile.java deleted file mode 100644 index c19356ef3..000000000 --- a/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryWritableFile.java +++ /dev/null @@ -1,102 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.inmem; - -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.ReentrantLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; -import java.util.function.Consumer; -import java.util.function.Supplier; - -import org.cryptomator.filesystem.WritableFile; -import org.cryptomator.io.ByteBuffers; - -public class InMemoryWritableFile implements WritableFile { - - private final Supplier contentGetter; - private final Consumer contentSetter; - private final WriteLock writeLock; - private final AtomicInteger position = new AtomicInteger(); - private final AtomicBoolean open = new AtomicBoolean(true); - private final ReentrantLock writingLock = new ReentrantLock(); - - public InMemoryWritableFile(Supplier contentGetter, Consumer contentSetter, WriteLock writeLock) { - this.contentGetter = contentGetter; - this.contentSetter = contentSetter; - this.writeLock = writeLock; - } - - @Override - public boolean isOpen() { - return open.get(); - } - - @Override - public void truncate() throws UncheckedIOException { - writingLock.lock(); - try { - contentSetter.accept(InMemoryFile.createNewEmptyByteBuffer()); - position.set(0); - } finally { - writingLock.unlock(); - } - } - - @Override - public int write(ByteBuffer source) throws UncheckedIOException { - writingLock.lock(); - try { - ByteBuffer content = contentGetter.get(); - int prevLimit = content.limit(); - - int toBeCopied = source.remaining(); - int pos = position.getAndAdd(toBeCopied); - int ourLimit = pos + toBeCopied; - int newLimit = Math.max(prevLimit, ourLimit); - - ByteBuffer destination = ensureCapacity(content, newLimit); - destination.limit(newLimit).position(pos); - return ByteBuffers.copy(source, destination); - } finally { - writingLock.unlock(); - } - } - - private ByteBuffer ensureCapacity(ByteBuffer buf, int limit) { - assert writingLock.isHeldByCurrentThread(); - if (buf.capacity() < limit) { - int oldPos = buf.position(); - buf.clear(); - int newBufferSize = Math.max(limit, (int) (buf.capacity() * InMemoryFile.GROWTH_RATE)); - ByteBuffer newBuf = ByteBuffer.allocate(newBufferSize); - ByteBuffers.copy(buf, newBuf); - newBuf.limit(limit).position(oldPos); - contentSetter.accept(newBuf); - return newBuf; - } else { - return buf; - } - } - - @Override - public void position(long position) throws UncheckedIOException { - assert position < Integer.MAX_VALUE : "Can not use that big in-memory files."; - this.position.set((int) position); - } - - @Override - public void close() throws UncheckedIOException { - open.set(false); - writeLock.unlock(); - } - -} diff --git a/main/filesystem-inmemory/src/test/java/org/cryptomator/filesystem/inmem/InMemoryFileSystemTest.java b/main/filesystem-inmemory/src/test/java/org/cryptomator/filesystem/inmem/InMemoryFileSystemTest.java deleted file mode 100644 index d262e2513..000000000 --- a/main/filesystem-inmemory/src/test/java/org/cryptomator/filesystem/inmem/InMemoryFileSystemTest.java +++ /dev/null @@ -1,168 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.inmem; - -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.time.Instant; -import java.util.concurrent.TimeoutException; - -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.filesystem.WritableFile; -import org.junit.Assert; -import org.junit.Test; - -public class InMemoryFileSystemTest { - - @Test - public void testFolderCreation() { - final FileSystem fs = new InMemoryFileSystem(); - Folder fooFolder = fs.folder("foo"); - - // nothing happened yet: - Assert.assertFalse(fooFolder.exists()); - Assert.assertEquals(0, fs.folders().count()); - - // create /foo - fooFolder.create(); - Assert.assertTrue(fooFolder.exists()); - Assert.assertEquals(1, fs.folders().count()); - - // delete /foo - fooFolder.delete(); - Assert.assertFalse(fooFolder.exists()); - Assert.assertEquals(0, fs.folders().count()); - - // create /foo/bar - Folder fooBarFolder = fooFolder.folder("bar"); - Assert.assertFalse(fooBarFolder.exists()); - fooBarFolder.create(); - Assert.assertTrue(fooFolder.exists()); - Assert.assertTrue(fooBarFolder.exists()); - Assert.assertEquals(1, fs.folders().count()); - Assert.assertEquals(1, fooFolder.folders().count()); - } - - @Test - public void testImplicitUpdateOfModifiedDateAfterWrite() throws UncheckedIOException, TimeoutException, InterruptedException { - final FileSystem fs = new InMemoryFileSystem(); - File fooFile = fs.file("foo.txt"); - - final Instant beforeFirstModification = Instant.now(); - - Thread.sleep(1); - - // write "hello world" to foo - try (WritableFile writable = fooFile.openWritable()) { - writable.write(ByteBuffer.wrap("hello world".getBytes())); - } - Assert.assertTrue(fooFile.exists()); - final Instant firstModification = fooFile.lastModified(); - - Thread.sleep(1); - - final Instant afterFirstModification = Instant.now(); - Assert.assertTrue(beforeFirstModification.isBefore(firstModification)); - Assert.assertTrue(afterFirstModification.isAfter(firstModification)); - - Thread.sleep(1); - - // write "dlrow olleh" to foo - try (WritableFile writable = fooFile.openWritable()) { - writable.write(ByteBuffer.wrap("dlrow olleh".getBytes())); - } - Assert.assertTrue(fooFile.exists()); - final Instant secondModification = fooFile.lastModified(); - - Assert.assertTrue(firstModification.isBefore(secondModification)); - } - - @Test - public void testFileReadCopyMoveWrite() throws TimeoutException { - final FileSystem fs = new InMemoryFileSystem(); - File fooFile = fs.file("foo.txt"); - - // nothing happened yet: - Assert.assertFalse(fooFile.exists()); - Assert.assertEquals(0, fs.files().count()); - - // write "hello world" to foo - try (WritableFile writable = fooFile.openWritable()) { - writable.write(ByteBuffer.wrap("hello".getBytes())); - writable.write(ByteBuffer.wrap(" ".getBytes())); - writable.write(ByteBuffer.wrap("world".getBytes())); - } - Assert.assertTrue(fooFile.exists()); - - // check if size = 11 bytes - Assert.assertEquals(11, fooFile.size()); - - // copy foo to bar - File barFile = fs.file("bar.txt"); - fooFile.copyTo(barFile); - Assert.assertTrue(fooFile.exists()); - Assert.assertTrue(barFile.exists()); - - // move bar to baz - File bazFile = fs.file("baz.txt"); - barFile.moveTo(bazFile); - Assert.assertFalse(barFile.exists()); - Assert.assertTrue(bazFile.exists()); - - // read "hello world" from baz - final ByteBuffer readBuf1 = ByteBuffer.allocate(6); - try (ReadableFile readable = bazFile.openReadable()) { - readable.read(readBuf1); - readBuf1.flip(); - Assert.assertEquals("hello ", new String(readBuf1.array(), 0, readBuf1.remaining())); - readable.read(readBuf1); - readBuf1.flip(); - Assert.assertEquals("world", new String(readBuf1.array(), 0, readBuf1.remaining())); - } - final ByteBuffer readBuf = ByteBuffer.allocate(5); - try (ReadableFile readable = bazFile.openReadable()) { - readable.position(6); - readable.read(readBuf); - } - Assert.assertEquals("world", new String(readBuf.array())); - } - - @Test - public void testFolderCopy() throws TimeoutException { - final FileSystem fs = new InMemoryFileSystem(); - final Folder fooBarFolder = fs.folder("foo").folder("bar"); - final Folder qweAsdFolder = fs.folder("qwe").folder("asd"); - final File test1File = fooBarFolder.file("test1.txt"); - final File test2File = fooBarFolder.file("test2.txt"); - fooBarFolder.create(); - - // create some files inside foo/bar/ - try (WritableFile writable1 = test1File.openWritable(); // - WritableFile writable2 = test2File.openWritable()) { - writable1.write(ByteBuffer.wrap("hello".getBytes())); - writable2.write(ByteBuffer.wrap("world".getBytes())); - } - Assert.assertTrue(test1File.exists()); - Assert.assertTrue(test2File.exists()); - - // copy foo/bar/ to qwe/asd/ (result is qwe/asd/file1.txt & - // qwe/asd/file2.txt) - fooBarFolder.copyTo(qweAsdFolder); - Assert.assertTrue(qweAsdFolder.exists()); - Assert.assertEquals(2, qweAsdFolder.files().count()); - - // make sure original files still exist: - Assert.assertTrue(test1File.exists()); - Assert.assertTrue(test2File.exists()); - } - -} diff --git a/main/filesystem-inmemory/src/test/java/org/cryptomator/filesystem/inmem/InMemoryFileTest.java b/main/filesystem-inmemory/src/test/java/org/cryptomator/filesystem/inmem/InMemoryFileTest.java deleted file mode 100644 index d70f8f242..000000000 --- a/main/filesystem-inmemory/src/test/java/org/cryptomator/filesystem/inmem/InMemoryFileTest.java +++ /dev/null @@ -1,66 +0,0 @@ -/******************************************************************************* - * 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.inmem; - -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; - -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.time.Instant; - -import org.cryptomator.filesystem.WritableFile; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -public class InMemoryFileTest { - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @Test - public void testCreationTimeOfNonExistingFileThrowsUncheckedIOException() { - InMemoryFileSystem fileSystem = new InMemoryFileSystem(); - InMemoryFile file = fileSystem.file("foo"); - - thrown.expect(UncheckedIOException.class); - - file.creationTime(); - } - - @Test - public void testCreationTimeOfCreatedFileIsSetToInstantDuringCreation() { - InMemoryFileSystem fileSystem = new InMemoryFileSystem(); - InMemoryFile file = fileSystem.file("foo"); - - Instant minCreationTime = Instant.now(); - Instant maxCreationTime; - try (WritableFile writable = file.openWritable()) { - maxCreationTime = Instant.now(); - } - - assertThat(file.creationTime().get().isBefore(minCreationTime), is(false)); - assertThat(file.creationTime().get().isAfter(maxCreationTime), is(false)); - } - - @Test - public void testCreationTimeSetIsSaved() { - Instant creationTime = Instant.parse("2015-03-23T21:11:32Z"); - InMemoryFileSystem fileSystem = new InMemoryFileSystem(); - InMemoryFile file = fileSystem.file("foo"); - try (WritableFile writable = file.openWritable()) { - writable.write(ByteBuffer.allocate(0)); - } - - file.setCreationTime(creationTime); - assertThat(file.creationTime().get(), is(creationTime)); - } - -} diff --git a/main/filesystem-inmemory/src/test/resources/log4j2.xml b/main/filesystem-inmemory/src/test/resources/log4j2.xml deleted file mode 100644 index 9b4889392..000000000 --- a/main/filesystem-inmemory/src/test/resources/log4j2.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/main/filesystem-invariants-tests/.gitignore b/main/filesystem-invariants-tests/.gitignore deleted file mode 100644 index a6f89c2da..000000000 --- a/main/filesystem-invariants-tests/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target/ \ No newline at end of file diff --git a/main/filesystem-invariants-tests/pom.xml b/main/filesystem-invariants-tests/pom.xml deleted file mode 100644 index 0a01cd0f2..000000000 --- a/main/filesystem-invariants-tests/pom.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - 4.0.0 - - org.cryptomator - main - 1.3.0-SNAPSHOT - - filesystem-invariants-tests - Cryptomator filesystem: Invariants tests - Test only project which checks invariants of FileSystem implementations - - - - org.cryptomator - filesystem-api - - - org.cryptomator - filesystem-charsets - - - org.cryptomator - filesystem-crypto - - - org.cryptomator - filesystem-crypto-integration-tests - - - org.cryptomator - filesystem-inmemory - - - org.cryptomator - filesystem-nameshortening - - - org.cryptomator - filesystem-nio - - - org.cryptomator - filesystem-stats - - - org.cryptomator - commons-test - - - - com.google.dagger - dagger - - - com.google.dagger - dagger-compiler - provided - - - - - - - org.jacoco - jacoco-maven-plugin - - - - \ No newline at end of file diff --git a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/ConcurrencyTests.java b/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/ConcurrencyTests.java deleted file mode 100644 index c924f81d8..000000000 --- a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/ConcurrencyTests.java +++ /dev/null @@ -1,134 +0,0 @@ -package org.cryptomator.filesystem.invariants; - -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assume.assumeThat; - -import java.nio.ByteBuffer; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.SynchronousQueue; - -import org.cryptomator.common.RunnableThrowingException; -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.filesystem.invariants.FileSystemFactories.FileSystemFactory; -import org.cryptomator.filesystem.invariants.WaysToObtainAFile.WayToObtainAFile; -import org.cryptomator.filesystem.invariants.WaysToObtainAFolder.WayToObtainAFolder; -import org.junit.Rule; -import org.junit.experimental.theories.DataPoints; -import org.junit.experimental.theories.Theories; -import org.junit.experimental.theories.Theory; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; - -@RunWith(Theories.class) -public class ConcurrencyTests { - - @DataPoints - public static final Iterable FILE_SYSTEM_FACTORIES = new FileSystemFactories(); - - @DataPoints - public static final Iterable WAYS_TO_OBTAIN_A_FOLDER = new WaysToObtainAFolder(); - - @DataPoints - public static final Iterable WAYS_TO_OBTAIN_A_FILE = new WaysToObtainAFile(); - - private static final String FILE_NAME = "fileName"; - - @Rule - public final ExpectedException thrown = ExpectedException.none(); - - @Theory - public void testConcurrentPartialReadsDontInterfere(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainAnExistingFile) throws ExecutionException { - assumeThat(wayToObtainAnExistingFile.returnedFilesExist(), is(true)); - - FileSystem fileSystem = fileSystemFactory.create(); - byte[] originalData = new byte[] {32, 44, 1, -3, 4, 66, 4}; - byte[] expectedData1 = new byte[] {-3, 4, 66}; - byte[] expectedData2 = new byte[] {44, 1, -3, 4}; - File file = wayToObtainAnExistingFile.fileWithNameAndContent(fileSystem, FILE_NAME, originalData); - - TasksInThreadRunner thread1 = new TasksInThreadRunner(); - TasksInThreadRunner thread2 = new TasksInThreadRunner(); - - Holder readableFile1 = new Holder<>(); - Holder readableFile2 = new Holder<>(); - byte[] actualData1 = new byte[3]; - byte[] actualData2 = new byte[4]; - - thread1.runAndWaitFor(() -> readableFile1.value = file.openReadable()); - thread2.runAndWaitFor(() -> readableFile2.value = file.openReadable()); - thread1.runAndWaitFor(() -> readableFile1.value.position(3)); - thread2.runAndWaitFor(() -> { - readableFile2.value.position(1); - readableFile2.value.read(ByteBuffer.wrap(actualData2)); - }); - thread1.runAndWaitFor(() -> readableFile1.value.read(ByteBuffer.wrap(actualData1))); - thread1.runAndWaitFor(readableFile1.value::close); - thread2.runAndWaitFor(readableFile2.value::close); - - thread1.shutdown(); - thread2.shutdown(); - - assertArrayEquals(expectedData1, actualData1); - assertArrayEquals(expectedData2, actualData2); - } - - private static class Holder { - - T value; - - } - - private static class TasksInThreadRunner { - - private final Runnable TERMINATION_HINT = () -> { - }; - - private final SynchronousQueue handoverQueue = new SynchronousQueue<>(); - private final Thread thread = new Thread(() -> { - try { - Runnable task; - while ((task = handoverQueue.take()) != TERMINATION_HINT) { - task.run(); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return; - } - }); - - public TasksInThreadRunner() { - thread.start(); - } - - public void runAndWaitFor(RunnableThrowingException task) throws ExecutionException { - CompletableFuture future = new CompletableFuture<>(); - try { - handoverQueue.put(() -> { - try { - task.run(); - future.complete(null); - } catch (Throwable e) { - future.completeExceptionally(e); - } - }); - future.get(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - public void shutdown() { - try { - handoverQueue.put(TERMINATION_HINT); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - } - -} diff --git a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FileReadWriteTests.java b/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FileReadWriteTests.java deleted file mode 100644 index 5999b35e1..000000000 --- a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FileReadWriteTests.java +++ /dev/null @@ -1,133 +0,0 @@ -package org.cryptomator.filesystem.invariants; - -import static org.cryptomator.filesystem.invariants.matchers.NodeMatchers.hasContent; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.junit.Assert.assertThat; -import static org.junit.Assume.assumeThat; - -import java.nio.ByteBuffer; - -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.WritableFile; -import org.cryptomator.filesystem.invariants.FileSystemFactories.FileSystemFactory; -import org.cryptomator.filesystem.invariants.WaysToObtainAFile.WayToObtainAFile; -import org.cryptomator.filesystem.invariants.WaysToObtainAFolder.WayToObtainAFolder; -import org.junit.Rule; -import org.junit.experimental.theories.DataPoints; -import org.junit.experimental.theories.Theories; -import org.junit.experimental.theories.Theory; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; - -@RunWith(Theories.class) -public class FileReadWriteTests { - - @DataPoints - public static final Iterable FILE_SYSTEM_FACTORIES = new FileSystemFactories(); - - @DataPoints - public static final Iterable WAYS_TO_OBTAIN_A_FOLDER = new WaysToObtainAFolder(); - - @DataPoints - public static final Iterable WAYS_TO_OBTAIN_A_FILE = new WaysToObtainAFile(); - - private static final String FILE_NAME = "fileName"; - - @Rule - public final ExpectedException thrown = ExpectedException.none(); - - @Theory - public void testWriteToNonExistingFileCreatesFileWithContent(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainANonExistingFile) { - assumeThat(wayToObtainANonExistingFile.returnedFilesExist(), is(false)); - FileSystem fileSystem = fileSystemFactory.create(); - File file = wayToObtainANonExistingFile.fileWithName(fileSystem, FILE_NAME); - byte[] dataToWrite = new byte[] {42, -43, 111, 104, -3, 83, -99, 30}; - - try (WritableFile writable = file.openWritable()) { - writable.write(ByteBuffer.wrap(dataToWrite)); - } - - assertThat(file, hasContent(dataToWrite)); - } - - @Theory - public void testWriteToExistingFileOverwritesContent(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainAnExistingFile) { - assumeThat(wayToObtainAnExistingFile.returnedFilesExist(), is(true)); - FileSystem fileSystem = fileSystemFactory.create(); - byte[] originalData = new byte[] {32, 44, 1, -3, 4, 66, 4}; - File file = wayToObtainAnExistingFile.fileWithNameAndContent(fileSystem, FILE_NAME, originalData); - byte[] dataToWrite = new byte[] {42, -43, 111, 104, -3, 83, -99, 30}; - - try (WritableFile writable = file.openWritable()) { - writable.write(ByteBuffer.wrap(dataToWrite)); - } - - assertThat(file, hasContent(dataToWrite)); - } - - @Theory - public void testPartialWriteAtStartOfExistingFileOverwritesOnlyPartOfContents(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainAnExistingFile) { - assumeThat(wayToObtainAnExistingFile.returnedFilesExist(), is(true)); - - // TODO implement partial writes in CryptoFileSystem - assumeThat(fileSystemFactory.toString(), not(containsString("Crypto"))); - - FileSystem fileSystem = fileSystemFactory.create(); - byte[] originalData = new byte[] {32, 44, 1, -3, 4, 66, 4}; - byte[] dataToWrite = new byte[] {1, 2, 3, 4}; - byte[] expectedData = new byte[] {1, 2, 3, 4, 4, 66, 4}; - File file = wayToObtainAnExistingFile.fileWithNameAndContent(fileSystem, FILE_NAME, originalData); - - try (WritableFile writable = file.openWritable()) { - writable.write(ByteBuffer.wrap(dataToWrite)); - } - - assertThat(file, hasContent(expectedData)); - } - - @Theory - public void testPartialWriteInTheMiddleOfExistingFileOverwritesOnlyPartOfContents(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainAnExistingFile) { - assumeThat(wayToObtainAnExistingFile.returnedFilesExist(), is(true)); - - // TODO implement partial writes in CryptoFileSystem - assumeThat(fileSystemFactory.toString(), not(containsString("Crypto"))); - - FileSystem fileSystem = fileSystemFactory.create(); - byte[] originalData = new byte[] {32, 44, 1, -3, 4, 66, 4}; - byte[] dataToWrite = new byte[] {3, 4, 5, 6}; - byte[] expectedData = new byte[] {32, 44, 3, 4, 5, 6, 4}; - File file = wayToObtainAnExistingFile.fileWithNameAndContent(fileSystem, FILE_NAME, originalData); - - try (WritableFile writable = file.openWritable()) { - writable.position(2); - writable.write(ByteBuffer.wrap(dataToWrite)); - } - - assertThat(file, hasContent(expectedData)); - } - - @Theory - public void testPartialWriteAtEndOfExistingFileOverwritesOnlyPartOfContents(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainAnExistingFile) { - assumeThat(wayToObtainAnExistingFile.returnedFilesExist(), is(true)); - - // TODO implement partial writes in CryptoFileSystem - assumeThat(fileSystemFactory.toString(), not(containsString("Crypto"))); - - FileSystem fileSystem = fileSystemFactory.create(); - byte[] originalData = new byte[] {-1, 44, 1, -3, 4, 66, 4}; - byte[] dataToWrite = new byte[] {4, 5, 6, 7}; - byte[] expectedData = new byte[] {-1, 44, 1, 4, 5, 6, 7}; - File file = wayToObtainAnExistingFile.fileWithNameAndContent(fileSystem, FILE_NAME, originalData); - - try (WritableFile writable = file.openWritable()) { - writable.position(3); - writable.write(ByteBuffer.wrap(dataToWrite)); - } - - assertThat(file, hasContent(expectedData)); - } - -} 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 deleted file mode 100644 index 8f1eed8ca..000000000 --- a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FileSystemFactories.java +++ /dev/null @@ -1,144 +0,0 @@ -package org.cryptomator.filesystem.invariants; - -import static org.cryptomator.common.test.TempFilesRemovedOnShutdown.createTempDirectory; - -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; -import org.cryptomator.filesystem.crypto.DaggerCryptoFileSystemTestComponent; -import org.cryptomator.filesystem.inmem.InMemoryFileSystem; -import org.cryptomator.filesystem.invariants.FileSystemFactories.FileSystemFactory; -import org.cryptomator.filesystem.nio.NioFileSystem; -import org.cryptomator.filesystem.shortening.ShorteningFileSystem; -import org.cryptomator.filesystem.stats.StatsFileSystem; -import org.mockito.Mockito; - -class FileSystemFactories implements Iterable { - - private static final CryptoFileSystemTestComponent CRYPTO_FS_COMP = DaggerCryptoFileSystemTestComponent.builder().cryptoEngineModule(new CryptoEngineTestModule()).build(); - - private final List factories = new ArrayList<>(); - - public FileSystemFactories() { - add("NioFileSystem", this::createNioFileSystem); - add("InMemoryFileSystem", this::createInMemoryFileSystem); - add("CryptoFileSystem > NioFileSystem", this::createCryptoFileSystemNio); - add("CryptoFileSystem > InMemoryFileSystem", this::createCryptoFileSystemInMemory); - add("ShorteningFileSystem > NioFileSystem", this::createShorteningFileSystemNio); - add("ShorteningFileSystem > InMemoryFileSystem", this::createShorteningFileSystemInMemory); - add("StatsFileSystem > NioFileSystem", this::createStatsFileSystemNio); - add("StatsFileSystem > InMemoryFileSystem", this::createStatsFileSystemInMemory); - 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() { - return createCryptoFileSystem(createInMemoryFileSystem()); - } - - private FileSystem createCryptoFileSystemNio() { - return createCryptoFileSystem(createNioFileSystem()); - } - - private FileSystem createShorteningFileSystemNio() { - return createShorteningFileSystem(createNioFileSystem()); - } - - private FileSystem createShorteningFileSystemInMemory() { - return createShorteningFileSystem(createInMemoryFileSystem()); - } - - private FileSystem createStatsFileSystemNio() { - return createStatsFileSystem(createNioFileSystem()); - } - - private FileSystem createStatsFileSystemInMemory() { - return createStatsFileSystem(createInMemoryFileSystem()); - } - - private FileSystem createNormalizingFileSystemNio() { - return createNormalizingFileSystem(createInMemoryFileSystem()); - } - - private FileSystem createNormalizingFileSystemInMemory() { - return createNormalizingFileSystem(createInMemoryFileSystem()); - } - - private FileSystem createCompoundFileSystemNio() { - return createCompoundFileSystem(createNioFileSystem()); - } - - private FileSystem createCompoundFileSystemInMemory() { - return createCompoundFileSystem(createInMemoryFileSystem()); - } - - private FileSystem createNioFileSystem() { - try { - return NioFileSystem.rootedAt(createTempDirectory("fileSystemToTest")); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - private FileSystem createInMemoryFileSystem() { - return new InMemoryFileSystem(); - } - - private FileSystem createCompoundFileSystem(FileSystem 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)); - } - - private FileSystem createShorteningFileSystem(FileSystem delegate) { - return new ShorteningFileSystem(delegate, "m", 3); - } - - private void add(String name, FileSystemFactory factory) { - factories.add(new FileSystemFactory() { - @Override - public FileSystem create() { - return factory.create(); - } - - @Override - public String toString() { - return name; - } - }); - } - - @Override - public Iterator iterator() { - return factories.iterator(); - } - - public interface FileSystemFactory { - - FileSystem create(); - - } - -} diff --git a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FileSystemTests.java b/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FileSystemTests.java deleted file mode 100644 index b2ccd1c5c..000000000 --- a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FileSystemTests.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.cryptomator.filesystem.invariants; - -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; - -import java.util.Optional; - -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.invariants.FileSystemFactories.FileSystemFactory; -import org.junit.Rule; -import org.junit.experimental.theories.DataPoints; -import org.junit.experimental.theories.Theories; -import org.junit.experimental.theories.Theory; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; - -@RunWith(Theories.class) -public class FileSystemTests { - - @DataPoints - public static final Iterable FILE_SYSTEM_FACTORIES = new FileSystemFactories(); - - @Rule - public final ExpectedException thrown = ExpectedException.none(); - - @Theory - public void testFileSystemHasNoParent(FileSystemFactory factory) { - FileSystem inTest = factory.create(); - - assertThat(inTest.parent(), is(Optional.empty())); - } - - @Theory - public void testFileSystemExists(FileSystemFactory factory) { - FileSystem inTest = factory.create(); - - assertThat(inTest.exists(), is(true)); - } - - @Theory - public void testFileSystemHasNoChildren(FileSystemFactory factory) { - FileSystem inTest = factory.create(); - - assertThat(inTest.children().count(), is(0L)); - } - - @Theory - public void testFileSystemHasNoFiles(FileSystemFactory factory) { - FileSystem inTest = factory.create(); - - assertThat(inTest.files().count(), is(0L)); - } - - @Theory - public void testFileSystemHasNoFolders(FileSystemFactory factory) { - FileSystem inTest = factory.create(); - - assertThat(inTest.folders().count(), is(0L)); - } - - @Theory - public void testFileSystemsFileSystemIsItself(FileSystemFactory factory) { - FileSystem inTest = factory.create(); - - assertThat(inTest.fileSystem(), is(inTest)); - } - - @Theory - public void testFileSystemBelongsToSameFilesystemWhenCheckingItself(FileSystemFactory factory) { - FileSystem inTest = factory.create(); - - assertThat(inTest.belongsToSameFilesystem(inTest), is(true)); - } - - @Theory - public void testFileSystemDoesNotBelongToSameFilesystemWhenCheckingOtherInstance(FileSystemFactory factory) { - FileSystem inTest = factory.create(); - FileSystem otherInstance = factory.create(); - - assertThat(inTest.belongsToSameFilesystem(otherInstance), is(false)); - } - - @Theory - public void testFileSystemIsNoAncestorOfItself(FileSystemFactory factory) { - FileSystem inTest = factory.create(); - - assertThat(inTest.isAncestorOf(inTest), is(false)); - } - -} diff --git a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FileTests.java b/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FileTests.java deleted file mode 100644 index e3a4ab25c..000000000 --- a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FileTests.java +++ /dev/null @@ -1,204 +0,0 @@ -package org.cryptomator.filesystem.invariants; - -import static org.cryptomator.common.test.matcher.OptionalMatcher.presentOptionalWithValueThat; -import static org.cryptomator.filesystem.invariants.matchers.InstantMatcher.inRangeInclusiveWithTolerance; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; -import static org.junit.Assume.assumeThat; - -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.time.Instant; - -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.WritableFile; -import org.cryptomator.filesystem.invariants.FileSystemFactories.FileSystemFactory; -import org.cryptomator.filesystem.invariants.WaysToObtainAFile.WayToObtainAFile; -import org.cryptomator.filesystem.invariants.WaysToObtainAFolder.WayToObtainAFolder; -import org.junit.Rule; -import org.junit.experimental.theories.DataPoints; -import org.junit.experimental.theories.Theories; -import org.junit.experimental.theories.Theory; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; - -@RunWith(Theories.class) -public class FileTests { - - @DataPoints - public static final Iterable FILE_SYSTEM_FACTORIES = new FileSystemFactories(); - - @DataPoints - public static final Iterable WAYS_TO_OBTAIN_A_FOLDER = new WaysToObtainAFolder(); - - @DataPoints - public static final Iterable WAYS_TO_OBTAIN_A_FILE = new WaysToObtainAFile(); - - private static final String FILE_NAME = "fileName"; - - private static final String FOLDER_NAME = "folderName"; - - @Rule - public final ExpectedException thrown = ExpectedException.none(); - - @Theory - public void testNonExistingFileDoesNotExist(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainANonExistingFile) { - assumeThat(wayToObtainANonExistingFile.returnedFilesExist(), is(false)); - FileSystem fileSystem = fileSystemFactory.create(); - File file = wayToObtainANonExistingFile.fileWithName(fileSystem, FILE_NAME); - - assertThat(file.exists(), is(false)); - } - - @Theory - public void testExistingFileExist(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainAnExistingFile) { - assumeThat(wayToObtainAnExistingFile.returnedFilesExist(), is(true)); - FileSystem fileSystem = fileSystemFactory.create(); - File file = wayToObtainAnExistingFile.fileWithName(fileSystem, FILE_NAME); - - assertThat(file.exists(), is(true)); - } - - @Theory - public void testNameOfFileIsFileName(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainAFile) { - FileSystem fileSystem = fileSystemFactory.create(); - File file = wayToObtainAFile.fileWithName(fileSystem, FILE_NAME); - - assertThat(file.name(), is(FILE_NAME)); - } - - @Theory - public void testDeletedFileDoesNotExist(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainAFile) { - FileSystem fileSystem = fileSystemFactory.create(); - File file = wayToObtainAFile.fileWithName(fileSystem, FILE_NAME); - file.delete(); - - assertThat(file.exists(), is(false)); - } - - @Theory - public void testParentOfFileInFilesystemIsFilesystem(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainAFile) { - FileSystem fileSystem = fileSystemFactory.create(); - File file = wayToObtainAFile.fileWithName(fileSystem, FILE_NAME); - - assertThat(file.parent(), is(presentOptionalWithValueThat(is(fileSystem)))); - } - - @Theory - public void testParentOfFileInFolderIsFolder(FileSystemFactory fileSystemFactory, WayToObtainAFolder wayToObtainAFolder, WayToObtainAFile wayToObtainAFile) { - FileSystem fileSystem = fileSystemFactory.create(); - Folder folder = wayToObtainAFolder.folderWithName(fileSystem, FOLDER_NAME); - File file = wayToObtainAFile.fileWithName(folder, FILE_NAME); - - assertThat(file.parent(), is(presentOptionalWithValueThat(is(folder)))); - } - - @Theory - public void testFilesystemOfFileInFilesystemInFilesystem(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainAFile) { - FileSystem fileSystem = fileSystemFactory.create(); - File file = wayToObtainAFile.fileWithName(fileSystem, FILE_NAME); - - assertThat(file.fileSystem(), is(fileSystem)); - } - - @Theory - public void testFilesystemOfFileInFolderIsFilesystem(FileSystemFactory fileSystemFactory, WayToObtainAFolder wayToObtainAFolder, WayToObtainAFile wayToObtainAFile) { - FileSystem fileSystem = fileSystemFactory.create(); - Folder folder = wayToObtainAFolder.folderWithName(fileSystem, FOLDER_NAME); - File file = wayToObtainAFile.fileWithName(folder, FILE_NAME); - - assertThat(file.fileSystem(), is(fileSystem)); - } - - @Theory - public void testFilesFromTwoFileSystemsDoNotBelongToSameFilesystem(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainAFile) { - File file = wayToObtainAFile.fileWithName(fileSystemFactory.create(), FILE_NAME); - File otherFile = wayToObtainAFile.fileWithName(fileSystemFactory.create(), FILE_NAME); - - assertThat(file.belongsToSameFilesystem(otherFile), is(false)); - } - - @Theory - public void testFilesFromSameFileSystemsDoBelongToSameFilesystem(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainAFile) { - FileSystem fileSystem = fileSystemFactory.create(); - File file = wayToObtainAFile.fileWithName(fileSystem, FILE_NAME); - File otherFile = wayToObtainAFile.fileWithName(fileSystem, FILE_NAME); - - assertThat(file.belongsToSameFilesystem(otherFile), is(true)); - } - - @Theory - public void testFilesBelongToSameFilesystemAsItself(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainAFile) { - FileSystem fileSystem = fileSystemFactory.create(); - File file = wayToObtainAFile.fileWithName(fileSystem, FILE_NAME); - - assertThat(file.belongsToSameFilesystem(file), is(true)); - } - - @Theory - public void testLastModifiedIsInCorrectSecondsRange(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainAnExistingFile) { - assumeThat(wayToObtainAnExistingFile.returnedFilesExist(), is(true)); - - FileSystem fileSystem = fileSystemFactory.create(); - Instant min = Instant.now(); - File file = wayToObtainAnExistingFile.fileWithName(fileSystem, FILE_NAME); - Instant max = Instant.now(); - - assertThat(file.lastModified(), is(inRangeInclusiveWithTolerance(min, max, 2000))); - } - - @Theory - public void testLastModifiedThrowsUncheckedIoExceptionForNonExistingFile(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainANonExistingFile) { - assumeThat(wayToObtainANonExistingFile.returnedFilesExist(), is(false)); - - FileSystem fileSystem = fileSystemFactory.create(); - - thrown.expect(UncheckedIOException.class); - - System.out.println(wayToObtainANonExistingFile.fileWithName(fileSystem, FILE_NAME).lastModified()); - } - - @Theory - public void testCanNotOpenFileWhichExistsAsFolderForReading(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainANonExistingFile, WayToObtainAFolder wayToObtainAnExistingFolder) { - assumeThat(wayToObtainANonExistingFile.returnedFilesExist(), is(false)); - assumeThat(wayToObtainAnExistingFolder.returnedFoldersExist(), is(true)); - FileSystem fileSystem = fileSystemFactory.create(); - File file = wayToObtainANonExistingFile.fileWithName(fileSystem, FILE_NAME); - wayToObtainAnExistingFolder.folderWithName(fileSystem, FILE_NAME); - - thrown.expect(UncheckedIOException.class); - - file.openReadable(); - } - - @Theory - public void testCanNotCreateFileWhichExistsAsFolderByWriting(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainANonExistingFile, WayToObtainAFolder wayToObtainAnExistingFolder) { - assumeThat(wayToObtainANonExistingFile.returnedFilesExist(), is(false)); - assumeThat(wayToObtainAnExistingFolder.returnedFoldersExist(), is(true)); - - FileSystem fileSystem = fileSystemFactory.create(); - File file = wayToObtainANonExistingFile.fileWithName(fileSystem, FILE_NAME); - wayToObtainAnExistingFolder.folderWithName(fileSystem, FILE_NAME); - - thrown.expect(UncheckedIOException.class); - - try (WritableFile writable = file.openWritable()) { - ByteBuffer buffer = ByteBuffer.allocate(1); - writable.write(buffer); - } - } - - @Theory - public void testCanNotReadFromNonExistingFile(FileSystemFactory fileSystemFactory, WayToObtainAFile wayToObtainANonExistingFile) { - assumeThat(wayToObtainANonExistingFile.returnedFilesExist(), is(false)); - FileSystem fileSystem = fileSystemFactory.create(); - File file = wayToObtainANonExistingFile.fileWithName(fileSystem, FILE_NAME); - - thrown.expect(UncheckedIOException.class); - - file.openReadable(); - } - -} diff --git a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FolderChildrenTests.java b/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FolderChildrenTests.java deleted file mode 100644 index ba304b0ef..000000000 --- a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FolderChildrenTests.java +++ /dev/null @@ -1,229 +0,0 @@ -package org.cryptomator.filesystem.invariants; - -import static java.util.stream.Collectors.toList; -import static org.cryptomator.common.test.matcher.ContainsMatcher.containsInAnyOrder; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.Matchers.empty; -import static org.junit.Assert.assertThat; -import static org.junit.Assume.assumeThat; - -import java.io.UncheckedIOException; - -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.invariants.WaysToObtainAFile.WayToObtainAFile; -import org.cryptomator.filesystem.invariants.FileSystemFactories.FileSystemFactory; -import org.cryptomator.filesystem.invariants.WaysToObtainAFolder.WayToObtainAFolder; -import org.junit.Rule; -import org.junit.experimental.theories.DataPoints; -import org.junit.experimental.theories.Theories; -import org.junit.experimental.theories.Theory; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; - -@RunWith(Theories.class) -public class FolderChildrenTests { - - private static final String FOLDER_NAME = "folderName"; - - @DataPoints - public static final Iterable FILE_SYSTEM_FACTORIES = new FileSystemFactories(); - - @DataPoints - public static final Iterable WAYS_TO_OBTAIN_A_FOLDER = new WaysToObtainAFolder(); - - @DataPoints - public static final Iterable WAYS_TO_OBTAIN_A_FILE = new WaysToObtainAFile(); - - @Rule - public final ExpectedException thrown = ExpectedException.none(); - - @Theory - public void testChildrenThrowsExceptionIfFolderDoesNotExist(FileSystemFactory fileSystemFactory, WayToObtainAFolder folderFunction) { - assumeThat(folderFunction.returnedFoldersExist(), is(false)); - - FileSystem fileSystem = fileSystemFactory.create(); - Folder nonExistingFolder = folderFunction.folderWithName(fileSystem, FOLDER_NAME); - - thrown.expect(UncheckedIOException.class); - - nonExistingFolder.children(); - } - - @Theory - public void testFilesThrowsExceptionIfFolderDoesNotExist(FileSystemFactory fileSystemFactory, WayToObtainAFolder folderFunction) { - assumeThat(folderFunction.returnedFoldersExist(), is(false)); - - FileSystem fileSystem = fileSystemFactory.create(); - Folder nonExistingFolder = folderFunction.folderWithName(fileSystem, FOLDER_NAME); - - thrown.expect(UncheckedIOException.class); - - nonExistingFolder.files(); - } - - @Theory - public void testFoldersThrowsExceptionIfFolderDoesNotExist(FileSystemFactory fileSystemFactory, WayToObtainAFolder folderFunction) { - assumeThat(folderFunction.returnedFoldersExist(), is(false)); - - FileSystem fileSystem = fileSystemFactory.create(); - Folder nonExistingFolder = folderFunction.folderWithName(fileSystem, FOLDER_NAME); - - thrown.expect(UncheckedIOException.class); - - nonExistingFolder.folders(); - } - - @Theory - public void testChildrenIsEmptyForEmptyFolder(FileSystemFactory fileSystemFactory, WayToObtainAFolder folderFunction) { - assumeThat(folderFunction.returnedFoldersExist(), is(true)); - - FileSystem fileSystem = fileSystemFactory.create(); - Folder existingFolder = folderFunction.folderWithName(fileSystem, FOLDER_NAME); - - assertThat(existingFolder.children().count(), is(0L)); - } - - @Theory - public void testFilesIsEmptyForEmptyFolder(FileSystemFactory fileSystemFactory, WayToObtainAFolder folderFunction) { - assumeThat(folderFunction.returnedFoldersExist(), is(true)); - - FileSystem fileSystem = fileSystemFactory.create(); - Folder existingFolder = folderFunction.folderWithName(fileSystem, FOLDER_NAME); - - assertThat(existingFolder.files().count(), is(0L)); - } - - @Theory - public void testFoldersIsEmptyForEmptyFolder(FileSystemFactory fileSystemFactory, WayToObtainAFolder folderFunction) { - assumeThat(folderFunction.returnedFoldersExist(), is(true)); - - FileSystem fileSystem = fileSystemFactory.create(); - Folder existingFolder = folderFunction.folderWithName(fileSystem, FOLDER_NAME); - - assertThat(existingFolder.folders().count(), is(0L)); - } - - @Theory - public void testChildrenContainsCreatedChildFolder(FileSystemFactory fileSystemFactory, WayToObtainAFolder existingFolderFunction, WayToObtainAFolder childExistingFolderFunction) { - assumeThat(existingFolderFunction.returnedFoldersExist(), is(true)); - assumeThat(childExistingFolderFunction.returnedFoldersExist(), is(true)); - - String childName = "childFolderName"; - - FileSystem fileSystem = fileSystemFactory.create(); - Folder existingFolder = existingFolderFunction.folderWithName(fileSystem, FOLDER_NAME); - Folder childFolder = childExistingFolderFunction.folderWithName(existingFolder, childName); - - assertThat(existingFolder.children().collect(toList()), containsInAnyOrder(equalTo(childFolder))); - } - - @Theory - public void testChildrenDoesNotContainCreatedAndDeletedChildFolder(FileSystemFactory fileSystemFactory, WayToObtainAFolder existingFolderFunction, WayToObtainAFolder childExistingFolderFunction) { - assumeThat(existingFolderFunction.returnedFoldersExist(), is(true)); - assumeThat(childExistingFolderFunction.returnedFoldersExist(), is(true)); - - String childName = "childFolderName"; - - FileSystem fileSystem = fileSystemFactory.create(); - Folder existingFolder = existingFolderFunction.folderWithName(fileSystem, FOLDER_NAME); - Folder childFolder = childExistingFolderFunction.folderWithName(existingFolder, childName); - childFolder.delete(); - - assertThat(existingFolder.children().collect(toList()), is(empty())); - } - - @Theory - public void testChildrenContainsCreatedFile(FileSystemFactory fileSystemFactory, WayToObtainAFolder existingFolderFunction, WayToObtainAFile existingFileFunction) { - assumeThat(existingFolderFunction.returnedFoldersExist(), is(true)); - assumeThat(existingFileFunction.returnedFilesExist(), is(true)); - - String childName = "childFolderName"; - - FileSystem fileSystem = fileSystemFactory.create(); - Folder existingFolder = existingFolderFunction.folderWithName(fileSystem, FOLDER_NAME); - File file = existingFileFunction.fileWithName(existingFolder, childName); - - assertThat(existingFolder.children().collect(toList()), containsInAnyOrder(equalTo(file))); - } - - @Theory - public void testChildrenDoesNotContainCreatedAndDeletedFile(FileSystemFactory fileSystemFactory, WayToObtainAFolder existingFolderFunction, WayToObtainAFile existingFileFunction) { - assumeThat(existingFolderFunction.returnedFoldersExist(), is(true)); - assumeThat(existingFileFunction.returnedFilesExist(), is(true)); - - String childName = "childFolderName"; - - FileSystem fileSystem = fileSystemFactory.create(); - Folder existingFolder = existingFolderFunction.folderWithName(fileSystem, FOLDER_NAME); - File file = existingFileFunction.fileWithName(existingFolder, childName); - file.delete(); - - assertThat(existingFolder.children().collect(toList()), is(empty())); - } - - @Theory - public void testFoldersDoesNotContainAndFilesContainsCreatedFile(FileSystemFactory fileSystemFactory, WayToObtainAFolder existingFolderFunction, WayToObtainAFile existingFileFunction) { - assumeThat(existingFolderFunction.returnedFoldersExist(), is(true)); - assumeThat(existingFileFunction.returnedFilesExist(), is(true)); - - String childName = "childFolderName"; - - FileSystem fileSystem = fileSystemFactory.create(); - Folder existingFolder = existingFolderFunction.folderWithName(fileSystem, FOLDER_NAME); - File file = existingFileFunction.fileWithName(existingFolder, childName); - - assertThat(existingFolder.folders().collect(toList()), is(empty())); - assertThat(existingFolder.files().collect(toList()), containsInAnyOrder(equalTo(file))); - } - - @Theory - public void testFoldersAndFilesDoesNotContainCreatedAndDeletedFile(FileSystemFactory fileSystemFactory, WayToObtainAFolder existingFolderFunction, WayToObtainAFile existingFileFunction) { - assumeThat(existingFolderFunction.returnedFoldersExist(), is(true)); - assumeThat(existingFileFunction.returnedFilesExist(), is(true)); - - String childName = "childFolderName"; - - FileSystem fileSystem = fileSystemFactory.create(); - Folder existingFolder = existingFolderFunction.folderWithName(fileSystem, FOLDER_NAME); - File file = existingFileFunction.fileWithName(existingFolder, childName); - file.delete(); - - assertThat(existingFolder.folders().collect(toList()), is(empty())); - assertThat(existingFolder.files().collect(toList()), is(empty())); - } - - @Theory - public void testFoldersContainsAndFilesDoesNotContainCreatedChildFolder(FileSystemFactory fileSystemFactory, WayToObtainAFolder existingFolderFunction, WayToObtainAFolder childExistingFolderFunction) { - assumeThat(existingFolderFunction.returnedFoldersExist(), is(true)); - assumeThat(childExistingFolderFunction.returnedFoldersExist(), is(true)); - - String childName = "childFolderName"; - - FileSystem fileSystem = fileSystemFactory.create(); - Folder existingFolder = existingFolderFunction.folderWithName(fileSystem, FOLDER_NAME); - Folder childFolder = childExistingFolderFunction.folderWithName(existingFolder, childName); - - assertThat(existingFolder.folders().collect(toList()), containsInAnyOrder(equalTo(childFolder))); - assertThat(existingFolder.files().collect(toList()), is(empty())); - } - - @Theory - public void testFoldersAndFilesDoesNotContainCreatedAndDeletedChildFolder(FileSystemFactory fileSystemFactory, WayToObtainAFolder existingFolderFunction, WayToObtainAFolder childExistingFolderFunction) { - assumeThat(existingFolderFunction.returnedFoldersExist(), is(true)); - assumeThat(childExistingFolderFunction.returnedFoldersExist(), is(true)); - - String childName = "childFolderName"; - - FileSystem fileSystem = fileSystemFactory.create(); - Folder existingFolder = existingFolderFunction.folderWithName(fileSystem, FOLDER_NAME); - Folder childFolder = childExistingFolderFunction.folderWithName(existingFolder, childName); - childFolder.delete(); - - assertThat(existingFolder.folders().collect(toList()), is(empty())); - assertThat(existingFolder.files().collect(toList()), is(empty())); - } - -} diff --git a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FolderCopyToTests.java b/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FolderCopyToTests.java deleted file mode 100644 index 2fc117f20..000000000 --- a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FolderCopyToTests.java +++ /dev/null @@ -1,143 +0,0 @@ -package org.cryptomator.filesystem.invariants; - -import static org.cryptomator.filesystem.invariants.matchers.NodeMatchers.hasContent; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; -import static org.junit.Assume.assumeThat; - -import java.io.UncheckedIOException; - -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.invariants.FileSystemFactories.FileSystemFactory; -import org.cryptomator.filesystem.invariants.WaysToObtainAFile.WayToObtainAFile; -import org.cryptomator.filesystem.invariants.WaysToObtainAFolder.WayToObtainAFolder; -import org.junit.Rule; -import org.junit.experimental.theories.DataPoints; -import org.junit.experimental.theories.Theories; -import org.junit.experimental.theories.Theory; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; - -@RunWith(Theories.class) -public class FolderCopyToTests { - - private static final String SOURCE_FOLDER_NAME = "sourceFolderName"; - private static final String TARGET_FOLDER_NAME = "targetFolderName"; - - @DataPoints - public static final Iterable FILE_SYSTEM_FACTORIES = new FileSystemFactories(); - - @DataPoints - public static final Iterable WAYS_TO_OBTAIN_A_FOLDER = new WaysToObtainAFolder(); - - @DataPoints - public static final Iterable WAYS_TO_OBTAIN_A_FILE = new WaysToObtainAFile(); - - @Rule - public final ExpectedException thrown = ExpectedException.none(); - - @Theory - public void testCopyAnExistingFolderToANonExistingFolderCreatesTheTargetFolder(FileSystemFactory fileSystemFactory, WayToObtainAFolder wayToObtainAnExistingFolder, WayToObtainAFolder wayToObtainANonExistingFolder) { - assumeThat(wayToObtainAnExistingFolder.returnedFoldersExist(), is(true)); - assumeThat(wayToObtainANonExistingFolder.returnedFoldersExist(), is(false)); - - FileSystem fileSystem = fileSystemFactory.create(); - - Folder source = wayToObtainAnExistingFolder.folderWithName(fileSystem, SOURCE_FOLDER_NAME); - Folder target = wayToObtainANonExistingFolder.folderWithName(fileSystem, TARGET_FOLDER_NAME); - - source.copyTo(target); - - assertThat(source.exists(), is(true)); - assertThat(target.exists(), is(true)); - } - - @Theory - public void testCopyAnExistingFolderToANonExistingFolderWhooseParentDoesNotExistCreatesTheParentAndTargetFolder(FileSystemFactory fileSystemFactory, WayToObtainAFolder wayToObtainAnExistingFolder, - WayToObtainAFolder wayToObtainANonExistingFolder) { - assumeThat(wayToObtainAnExistingFolder.returnedFoldersExist(), is(true)); - assumeThat(wayToObtainANonExistingFolder.returnedFoldersExist(), is(false)); - - FileSystem fileSystem = fileSystemFactory.create(); - - Folder source = wayToObtainAnExistingFolder.folderWithName(fileSystem, SOURCE_FOLDER_NAME); - Folder parentOfTarget = wayToObtainANonExistingFolder.folderWithName(fileSystem, TARGET_FOLDER_NAME); - Folder target = wayToObtainANonExistingFolder.folderWithName(parentOfTarget, TARGET_FOLDER_NAME); - - source.copyTo(target); - - assertThat(source.exists(), is(true)); - assertThat(parentOfTarget.exists(), is(true)); - assertThat(target.exists(), is(true)); - } - - @Theory - public void testCopyANonExistingFolderFails(FileSystemFactory fileSystemFactory, WayToObtainAFolder wayToObtainANonExistingFolder) { - assumeThat(wayToObtainANonExistingFolder.returnedFoldersExist(), is(false)); - - FileSystem fileSystem = fileSystemFactory.create(); - - Folder source = wayToObtainANonExistingFolder.folderWithName(fileSystem, SOURCE_FOLDER_NAME); - Folder target = wayToObtainANonExistingFolder.folderWithName(fileSystem, TARGET_FOLDER_NAME); - - thrown.expect(UncheckedIOException.class); - - source.copyTo(target); - } - - @Theory - public void testCopyAnExistingFolderToAnExistingFolderSucceeds(FileSystemFactory fileSystemFactory, WayToObtainAFolder wayToObtainAnExistingFolder) { - assumeThat(wayToObtainAnExistingFolder.returnedFoldersExist(), is(true)); - - FileSystem fileSystem = fileSystemFactory.create(); - - Folder source = wayToObtainAnExistingFolder.folderWithName(fileSystem, SOURCE_FOLDER_NAME); - Folder target = wayToObtainAnExistingFolder.folderWithName(fileSystem, TARGET_FOLDER_NAME); - - source.copyTo(target); - - assertThat(source.exists(), is(true)); - assertThat(target.exists(), is(true)); - } - - @Theory - public void testCopyAFolderWithChildrenCopiesChildrenRecursive(FileSystemFactory fileSystemFactory, WayToObtainAFolder wayToObtainAnExistingFolder, WayToObtainAFile wayToObtainAnExisitingFile, - WayToObtainAFolder wayToObtainANonExistingFolder) { - - assumeThat(wayToObtainAnExistingFolder.returnedFoldersExist(), is(true)); - assumeThat(wayToObtainANonExistingFolder.returnedFoldersExist(), is(false)); - assumeThat(wayToObtainAnExisitingFile.returnedFilesExist(), is(true)); - - String childFolderName = "childFolderName"; - String childFileName = "childFileName"; - String childFoldersChildFileName = "childFoldersChildFile"; - byte[] content1 = {23, 127, 3, 10, 101}; - byte[] content2 = {43, 22, 103, 67, 51, 5, 15, 93, 33}; - FileSystem fileSystem = fileSystemFactory.create(); - - Folder source = wayToObtainAnExistingFolder.folderWithName(fileSystem, SOURCE_FOLDER_NAME); - Folder childFolder = wayToObtainAnExistingFolder.folderWithName(source, childFolderName); - File childFile = wayToObtainAnExisitingFile.fileWithNameAndContent(source, childFileName, content1); - File childFoldersChildFile = wayToObtainAnExisitingFile.fileWithNameAndContent(childFolder, childFoldersChildFileName, content2); - - Folder target = wayToObtainANonExistingFolder.folderWithName(fileSystem, TARGET_FOLDER_NAME); - Folder targetChildFolder = target.folder(childFolderName); - File targetChildFile = target.file(childFileName); - File targetChildFoldersChildFile = targetChildFolder.file(childFoldersChildFileName); - - source.copyTo(target); - - assertThat(source.exists(), is(true)); - assertThat(childFolder.exists(), is(true)); - assertThat(childFile.exists(), is(true)); - assertThat(childFoldersChildFile.exists(), is(true)); - - assertThat(target.exists(), is(true)); - assertThat(targetChildFolder.exists(), is(true)); - assertThat(targetChildFile, hasContent(content1)); - assertThat(targetChildFoldersChildFile, hasContent(content2)); - } - -} diff --git a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FolderTests.java b/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FolderTests.java deleted file mode 100644 index d26d88cf7..000000000 --- a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FolderTests.java +++ /dev/null @@ -1,224 +0,0 @@ -package org.cryptomator.filesystem.invariants; - -import static org.cryptomator.common.test.matcher.OptionalMatcher.presentOptionalWithValueThat; -import static org.cryptomator.filesystem.invariants.matchers.NodeMatchers.fileWithName; -import static org.cryptomator.filesystem.invariants.matchers.NodeMatchers.folderWithName; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.junit.Assert.assertThat; -import static org.junit.Assume.assumeThat; - -import java.io.UncheckedIOException; - -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.invariants.FileSystemFactories.FileSystemFactory; -import org.cryptomator.filesystem.invariants.WaysToObtainAFile.WayToObtainAFile; -import org.cryptomator.filesystem.invariants.WaysToObtainAFolder.WayToObtainAFolder; -import org.junit.Rule; -import org.junit.experimental.theories.DataPoints; -import org.junit.experimental.theories.Theories; -import org.junit.experimental.theories.Theory; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; - -@RunWith(Theories.class) -public class FolderTests { - - private static final String FOLDER_NAME = "folderName"; - private static final String FOLDER_NAME_2 = "folderName2"; - - private static final String PATH_NAME_1 = "pathName1"; - private static final String PATH_NAME_2 = "pathName2"; - private static final String PATH = PATH_NAME_1 + '/' + PATH_NAME_2; - - private static final String FILE_NAME = "fileName"; - - @DataPoints - public static final Iterable FILE_SYSTEM_FACTORIES = new FileSystemFactories(); - - @DataPoints - public static final Iterable WAYS_TO_OBTAIN_A_FOLDER = new WaysToObtainAFolder(); - - @DataPoints - public static final Iterable WAYS_TO_OBTAIN_A_FILE = new WaysToObtainAFile(); - - @Rule - public final ExpectedException thrown = ExpectedException.none(); - - @Theory - public void testFolderReturnsFolder(FileSystemFactory fileSystemFactory) { - FileSystem fileSystem = fileSystemFactory.create(); - - assertThat(fileSystem.folder(FOLDER_NAME), is(notNullValue())); - } - - @Theory - public void testFolderOnSubfolderReturnsFolder(FileSystemFactory fileSystemFactory, WayToObtainAFolder folderBiFunction) { - FileSystem fileSystem = fileSystemFactory.create(); - - Folder folder = folderBiFunction.folderWithName(fileSystem, FOLDER_NAME); - - assertThat(folder.folder(FOLDER_NAME), is(notNullValue())); - } - - @Theory - public void testResolveFolderReturnsFolder(FileSystemFactory fileSystemFactory) { - FileSystem fileSystem = fileSystemFactory.create(); - - Folder resolvedFolder = fileSystem.resolveFolder(PATH); - - assertThat(resolvedFolder, is(folderWithName(PATH_NAME_2))); - assertThat(resolvedFolder.parent(), presentOptionalWithValueThat(is(folderWithName(PATH_NAME_1)))); - assertThat(resolvedFolder.parent().get().parent(), presentOptionalWithValueThat(is(fileSystem))); - } - - @Theory - public void testResolveFolderOnSubfolderReturnsFolder(FileSystemFactory fileSystemFactory, WayToObtainAFolder folderBiFunction) { - FileSystem fileSystem = fileSystemFactory.create(); - - Folder folder = folderBiFunction.folderWithName(fileSystem, FOLDER_NAME); - Folder resolvedFolder = folder.resolveFolder(PATH); - - assertThat(resolvedFolder, is(folderWithName(PATH_NAME_2))); - assertThat(resolvedFolder.parent(), presentOptionalWithValueThat(is(folderWithName(PATH_NAME_1)))); - assertThat(resolvedFolder.parent().get().parent(), presentOptionalWithValueThat(is(folder))); - } - - @Theory - public void testFileReturnsFile(FileSystemFactory fileSystemFactory) { - FileSystem fileSystem = fileSystemFactory.create(); - - assertThat(fileSystem.file(FILE_NAME), is(notNullValue())); - } - - @Theory - public void testFileOnSubfolderReturnsFile(FileSystemFactory fileSystemFactory, WayToObtainAFolder folderBiFunction) { - FileSystem fileSystem = fileSystemFactory.create(); - - Folder folder = folderBiFunction.folderWithName(fileSystem, FOLDER_NAME); - - assertThat(folder.file(FILE_NAME), is(notNullValue())); - } - - @Theory - public void testResolveFileReturnsFile(FileSystemFactory fileSystemFactory) { - FileSystem fileSystem = fileSystemFactory.create(); - - File resolvedFile = fileSystem.resolveFile(PATH); - - assertThat(resolvedFile, is(fileWithName(PATH_NAME_2))); - assertThat(resolvedFile.parent(), presentOptionalWithValueThat(is(folderWithName(PATH_NAME_1)))); - assertThat(resolvedFile.parent().get().parent(), presentOptionalWithValueThat(is(fileSystem))); - } - - @Theory - public void testResolveFileOnSubfolderReturnsFile(FileSystemFactory fileSystemFactory, WayToObtainAFolder folderBiFunction) { - FileSystem fileSystem = fileSystemFactory.create(); - - Folder folder = folderBiFunction.folderWithName(fileSystem, FOLDER_NAME); - File resolvedFile = folder.resolveFile(PATH); - - assertThat(resolvedFile, is(fileWithName(PATH_NAME_2))); - assertThat(resolvedFile.parent(), presentOptionalWithValueThat(is(folderWithName(PATH_NAME_1)))); - assertThat(resolvedFile.parent().get().parent(), presentOptionalWithValueThat(is(folder))); - } - - @Theory - public void testExistingFolderExists(FileSystemFactory fileSystemFactory, WayToObtainAFolder folderBiFunction) { - assumeThat(folderBiFunction.returnedFoldersExist(), is(true)); - - FileSystem fileSystem = fileSystemFactory.create(); - Folder existingFolder = folderBiFunction.folderWithName(fileSystem, FOLDER_NAME); - - assertThat(existingFolder.exists(), is(true)); - } - - @Theory - public void testNonExistingFolderDoesntExists(FileSystemFactory fileSystemFactory, WayToObtainAFolder folderBiFunction) { - assumeThat(folderBiFunction.returnedFoldersExist(), is(false)); - - FileSystem fileSystem = fileSystemFactory.create(); - Folder existingFolder = folderBiFunction.folderWithName(fileSystem, FOLDER_NAME); - - assertThat(existingFolder.exists(), is(false)); - } - - @Theory - public void testFolderIsNotAncecstorOfItself(FileSystemFactory fileSystemFactory, WayToObtainAFolder folderBiFunction) { - FileSystem fileSystem = fileSystemFactory.create(); - - Folder folder = folderBiFunction.folderWithName(fileSystem, FOLDER_NAME); - - assertThat(folder.isAncestorOf(folder), is(false)); - } - - @Theory - public void testFolderIsNotAncecstorOfItsParent(FileSystemFactory fileSystemFactory, WayToObtainAFolder folderBiFunction) { - FileSystem fileSystem = fileSystemFactory.create(); - - Folder parent = folderBiFunction.folderWithName(fileSystem, FOLDER_NAME); - Folder child = folderBiFunction.folderWithName(parent, FOLDER_NAME); - - assertThat(child.isAncestorOf(parent), is(false)); - } - - @Theory - public void testFolderIsNotAncecstorOfItsParentsParent(FileSystemFactory fileSystemFactory, WayToObtainAFolder folderBiFunction) { - FileSystem fileSystem = fileSystemFactory.create(); - - Folder parentsParent = folderBiFunction.folderWithName(fileSystem, FOLDER_NAME); - Folder parent = folderBiFunction.folderWithName(parentsParent, FOLDER_NAME); - Folder child = folderBiFunction.folderWithName(parent, FOLDER_NAME); - - assertThat(child.isAncestorOf(parentsParent), is(false)); - } - - @Theory - public void testFolderIsNotAncecstorOfItsSibling(FileSystemFactory fileSystemFactory, WayToObtainAFolder folderBiFunction) { - FileSystem fileSystem = fileSystemFactory.create(); - - Folder folder = folderBiFunction.folderWithName(fileSystem, FOLDER_NAME); - Folder sibling = folderBiFunction.folderWithName(fileSystem, FOLDER_NAME_2); - - assertThat(folder.isAncestorOf(sibling), is(false)); - } - - @Theory - public void testFolderIsAncecstorOfItsChild(FileSystemFactory fileSystemFactory, WayToObtainAFolder folderBiFunction) { - FileSystem fileSystem = fileSystemFactory.create(); - - Folder folder = folderBiFunction.folderWithName(fileSystem, FOLDER_NAME); - Folder child = folderBiFunction.folderWithName(folder, FOLDER_NAME); - - assertThat(folder.isAncestorOf(child), is(true)); - } - - @Theory - public void testFolderIsAncecstorOfItsChildsChild(FileSystemFactory fileSystemFactory, WayToObtainAFolder folderBiFunction) { - FileSystem fileSystem = fileSystemFactory.create(); - - Folder folder = folderBiFunction.folderWithName(fileSystem, FOLDER_NAME); - Folder child = folderBiFunction.folderWithName(folder, FOLDER_NAME); - Folder childsChild = folderBiFunction.folderWithName(child, FOLDER_NAME); - - assertThat(folder.isAncestorOf(childsChild), is(true)); - } - - @Theory - public void testFolderWhichExistsAsFileCanNotBeCreated(FileSystemFactory fileSystemFactory, WayToObtainAFolder wayToObtainANonExistingFolder, WayToObtainAFile wayToObtainAnExistingFile) { - assumeThat(wayToObtainAnExistingFile.returnedFilesExist(), is(true)); - assumeThat(wayToObtainANonExistingFolder.returnedFoldersExist(), is(false)); - - FileSystem fileSystem = fileSystemFactory.create(); - - Folder folder = wayToObtainANonExistingFolder.folderWithName(fileSystem, FOLDER_NAME); - wayToObtainAnExistingFile.fileWithName(fileSystem, FOLDER_NAME); - - thrown.expect(UncheckedIOException.class); - - folder.create(); - } - -} diff --git a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/WaysToObtainAFile.java b/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/WaysToObtainAFile.java deleted file mode 100644 index 204f3fde7..000000000 --- a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/WaysToObtainAFile.java +++ /dev/null @@ -1,135 +0,0 @@ -package org.cryptomator.filesystem.invariants; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.WritableFile; -import org.cryptomator.filesystem.invariants.WaysToObtainAFile.WayToObtainAFile; - -class WaysToObtainAFile implements Iterable { - - private final List values = new ArrayList<>(); - - public WaysToObtainAFile() { - addNonExisting("invoke file", this::invokeFile); - addNonExisting("delete file created by writing to it", this::deleteFileCreatedByWritingToIt); - - addExisting("create file by writing to it", this::createFileByWritingToIt); - addExisting("create file by copying", this::createFileByCopying); - addExisting("create file by moving", this::createFileByMoving); - } - - private File invokeFile(Folder parent, String name, byte[] content) { - return parent.file(name); - } - - private File deleteFileCreatedByWritingToIt(Folder parent, String name, byte[] content) { - boolean deleteParent = !parent.exists(); - parent.create(); - File result = parent.file(name); - try (WritableFile writable = result.openWritable()) { - writable.write(ByteBuffer.wrap(content)); - } - result.delete(); - if (deleteParent) { - parent.delete(); - } - return result; - } - - private File createFileByWritingToIt(Folder parent, String name, byte[] content) { - parent.create(); - File result = parent.file(name); - try (WritableFile writable = result.openWritable()) { - writable.write(ByteBuffer.wrap(content)); - } - return result; - } - - private File createFileByCopying(Folder parent, String name, byte[] content) { - parent.create(); - File tmp = parent.file(name + ".createFileByCopying.tmp"); - try (WritableFile writable = tmp.openWritable()) { - writable.write(ByteBuffer.wrap(content)); - } - File result = parent.file(name); - tmp.copyTo(result); - tmp.delete(); - return result; - } - - private File createFileByMoving(Folder parent, String name, byte[] content) { - parent.create(); - File tmp = parent.file(name + ".createFileByCopying.tmp"); - try (WritableFile writable = tmp.openWritable()) { - writable.write(ByteBuffer.wrap(content)); - } - File result = parent.file(name); - tmp.moveTo(result); - return result; - } - - private void addExisting(String name, WayToObtainAFileThatExists factory) { - values.add(new WayToObtainAFileThatExists() { - @Override - public File fileWithNameAndContent(Folder parent, String name, byte[] content) { - return factory.fileWithNameAndContent(parent, name, content); - } - - @Override - public String toString() { - return name; - } - }); - } - - private void addNonExisting(String name, WayToObtainAFileThatDoesntExist factory) { - values.add(new WayToObtainAFileThatDoesntExist() { - @Override - public File fileWithNameAndContent(Folder parent, String name, byte[] content) { - return factory.fileWithNameAndContent(parent, name, content); - } - - @Override - public String toString() { - return name; - } - }); - } - - public interface WayToObtainAFile { - - default File fileWithName(Folder parent, String name) { - return fileWithNameAndContent(parent, name, new byte[0]); - } - - File fileWithNameAndContent(Folder parent, String name, byte[] content); - - boolean returnedFilesExist(); - - } - - private interface WayToObtainAFileThatExists extends WayToObtainAFile { - @Override - default boolean returnedFilesExist() { - return true; - } - } - - private interface WayToObtainAFileThatDoesntExist extends WayToObtainAFile { - @Override - default boolean returnedFilesExist() { - return false; - } - } - - @Override - public Iterator iterator() { - return values.iterator(); - } - -} diff --git a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/WaysToObtainAFolder.java b/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/WaysToObtainAFolder.java deleted file mode 100644 index ac16b8f9f..000000000 --- a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/WaysToObtainAFolder.java +++ /dev/null @@ -1,122 +0,0 @@ -package org.cryptomator.filesystem.invariants; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.invariants.WaysToObtainAFolder.WayToObtainAFolder; - -class WaysToObtainAFolder implements Iterable { - - private final List values = new ArrayList<>(); - - public WaysToObtainAFolder() { - addNonExisting("invoke folder", this::invokeFolder); - addNonExisting("create and delete", this::createAndDeleteFolder); - addNonExisting("delete by moving", this::deleteFolderByMoving); - - addExisting("invoke folder and create", this::invokeFolderAndCreate); - addExisting("create by moving", this::createByMoving); - addExisting("create by copying", this::createByCopying); - } - - private Folder invokeFolder(Folder parent, String name) { - return parent.folder(name); - } - - private Folder invokeFolderAndCreate(Folder parent, String name) { - Folder result = parent.folder(name); - result.create(); - return result; - } - - private Folder createAndDeleteFolder(Folder parent, String name) { - Folder result = parent.folder(name); - result.create(); - result.delete(); - return result; - } - - private Folder deleteFolderByMoving(Folder parent, String name) { - Folder result = parent.folder(name); - result.create(); - Folder target = parent.folder("willNotExistMoveFolderAway"); - result.moveTo(target); - target.delete(); - return result; - } - - private Folder createByMoving(Folder parent, String name) { - Folder temporary = parent.folder("willNotExistCreateByMoving"); - temporary.create(); - Folder target = parent.folder(name); - temporary.moveTo(target); - return target; - } - - private Folder createByCopying(Folder parent, String name) { - Folder temporary = parent.folder("willNotExistCreateByCopying"); - temporary.create(); - Folder target = parent.folder(name); - temporary.copyTo(target); - temporary.delete(); - return target; - } - - private void addExisting(String name, WayToObtainAFolderThatExists factory) { - values.add(new WayToObtainAFolderThatExists() { - @Override - public Folder folderWithName(Folder parent, String name) { - return factory.folderWithName(parent, name); - } - - @Override - public String toString() { - return name; - } - }); - } - - private void addNonExisting(String name, WayToObtainAFolderThatDoesntExists factory) { - values.add(new WayToObtainAFolderThatDoesntExists() { - @Override - public Folder folderWithName(Folder parent, String name) { - return factory.folderWithName(parent, name); - } - - @Override - public String toString() { - return name; - } - }); - } - - public interface WayToObtainAFolder { - - Folder folderWithName(Folder parent, String name); - - boolean returnedFoldersExist(); - - } - - private interface WayToObtainAFolderThatExists extends WayToObtainAFolder { - @Override - default boolean returnedFoldersExist() { - return true; - } - } - - private interface WayToObtainAFolderThatDoesntExists extends WayToObtainAFolder { - @Override - default boolean returnedFoldersExist() { - return false; - } - } - - @Override - public Iterator iterator() { - return values.iterator(); - } - -} diff --git a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/matchers/InstantMatcher.java b/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/matchers/InstantMatcher.java deleted file mode 100644 index 3f13ae51a..000000000 --- a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/matchers/InstantMatcher.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.cryptomator.filesystem.invariants.matchers; - -import static java.lang.String.format; - -import java.time.Instant; - -import org.hamcrest.Description; -import org.hamcrest.Matcher; -import org.hamcrest.TypeSafeDiagnosingMatcher; - -public class InstantMatcher { - - public static Matcher inRangeInclusiveWithTolerance(Instant min, Instant max, int toleranceInMilliseconds) { - return inRangeInclusive(min.minusMillis(toleranceInMilliseconds), max.plusMillis(toleranceInMilliseconds)); - } - - public static Matcher inRangeInclusive(Instant min, Instant max) { - return new TypeSafeDiagnosingMatcher(Instant.class) { - @Override - public void describeTo(Description description) { - description.appendText(format("an Instant in [%s,%s]", min, max)); - } - - @Override - protected boolean matchesSafely(Instant item, Description mismatchDescription) { - if (item.isBefore(min) || item.isAfter(max)) { - mismatchDescription.appendText("the instant ").appendValue(item); - return false; - } - return true; - } - }; - } - -} diff --git a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/matchers/NodeMatchers.java b/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/matchers/NodeMatchers.java deleted file mode 100644 index 922f7f970..000000000 --- a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/matchers/NodeMatchers.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.cryptomator.filesystem.invariants.matchers; - -import static org.hamcrest.CoreMatchers.is; - -import java.nio.ByteBuffer; -import java.util.function.Function; - -import org.cryptomator.common.test.matcher.PropertyMatcher; -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.ReadableFile; -import org.hamcrest.Matcher; - -public class NodeMatchers { - - public static Matcher folderWithName(String name) { - return new PropertyMatcher<>(Folder.class, Folder::name, "name", is(name)); - } - - public static Matcher fileWithName(String name) { - return new PropertyMatcher<>(File.class, File::name, "name", is(name)); - } - - public static Matcher hasContent(byte[] content) { - return new PropertyMatcher<>(File.class, readFileContentWithLength(content.length), "content", is(content)); - } - - private static Function readFileContentWithLength(int expectedLength) { - return file -> { - try (ReadableFile readable = file.openReadable()) { - ByteBuffer buffer = ByteBuffer.allocate(expectedLength + 1); - readable.read(buffer); - if (buffer.remaining() == 0) { - throw new IllegalStateException("File content > expectedLength of " + expectedLength); - } - buffer.flip(); - byte[] result = new byte[buffer.limit()]; - buffer.get(result); - return result; - } - }; - } - -} diff --git a/main/filesystem-invariants-tests/src/test/resources/log4j2.xml b/main/filesystem-invariants-tests/src/test/resources/log4j2.xml deleted file mode 100644 index 9b4889392..000000000 --- a/main/filesystem-invariants-tests/src/test/resources/log4j2.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/main/filesystem-nameshortening/.gitignore b/main/filesystem-nameshortening/.gitignore deleted file mode 100644 index b83d22266..000000000 --- a/main/filesystem-nameshortening/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target/ diff --git a/main/filesystem-nameshortening/pom.xml b/main/filesystem-nameshortening/pom.xml deleted file mode 100644 index dc60fee28..000000000 --- a/main/filesystem-nameshortening/pom.xml +++ /dev/null @@ -1,76 +0,0 @@ - - - - 4.0.0 - - org.cryptomator - main - 1.3.0-SNAPSHOT - - filesystem-nameshortening - Cryptomator filesystem: Name shortening layer - - - - org.cryptomator - filesystem-api - - - org.cryptomator - commons - - - - - org.apache.commons - commons-lang3 - - - commons-codec - commons-codec - - - - - com.fasterxml.jackson.core - jackson-databind - - - - - com.google.dagger - dagger - - - com.google.dagger - dagger-compiler - provided - - - - - org.cryptomator - commons-test - - - org.cryptomator - filesystem-inmemory - - - - - - - org.jacoco - jacoco-maven-plugin - - - - \ No newline at end of file diff --git a/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/ConflictResolver.java b/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/ConflictResolver.java deleted file mode 100644 index ef5532767..000000000 --- a/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/ConflictResolver.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.cryptomator.filesystem.shortening; - -import java.util.UUID; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apache.commons.lang3.StringUtils; -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.Folder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -final class ConflictResolver { - - private static final Logger LOG = LoggerFactory.getLogger(ConflictResolver.class); - private static final String LONG_NAME_FILE_EXT = ".lng"; - private static final Pattern BASE32_PATTERN = Pattern.compile("^0?([A-Z2-7]{8})*[A-Z2-7=]{8}"); - private static final int UUID_FIRST_GROUP_STRLEN = 8; - - private ConflictResolver() { - } - - public static File resolveConflictIfNecessary(File potentiallyConflictingFile, FilenameShortener shortener) { - String shortName = potentiallyConflictingFile.name(); - String basename = StringUtils.removeEnd(shortName, LONG_NAME_FILE_EXT); - Matcher matcher = BASE32_PATTERN.matcher(basename); - if (shortName.endsWith(LONG_NAME_FILE_EXT) && matcher.matches()) { - // no conflict. - return potentiallyConflictingFile; - } else if (shortName.endsWith(LONG_NAME_FILE_EXT) && matcher.find(0)) { - String canonicalShortName = matcher.group() + LONG_NAME_FILE_EXT; - return resolveConflict(potentiallyConflictingFile, canonicalShortName, shortener); - } else { - // not even shortened at all. - return potentiallyConflictingFile; - } - } - - private static File resolveConflict(File conflictingFile, String canonicalShortName, FilenameShortener shortener) { - Folder parent = conflictingFile.parent().get(); - File canonicalFile = parent.file(canonicalShortName); - if (canonicalFile.exists()) { - // foo (1).lng -> bar.lng - String canonicalLongName = shortener.inflate(canonicalShortName); - String alternativeLongName; - String alternativeShortName; - File alternativeFile; - String conflictId; - do { - conflictId = createConflictId(); - alternativeLongName = canonicalLongName + " (Conflict " + conflictId + ")"; - alternativeShortName = shortener.deflate(alternativeLongName); - alternativeFile = parent.file(alternativeShortName); - } while (alternativeFile.exists()); - LOG.info("Detected conflict {}:\n{}\n{}", conflictId, canonicalFile, conflictingFile); - conflictingFile.moveTo(alternativeFile); - shortener.saveMapping(alternativeLongName, alternativeShortName); - return alternativeFile; - } else { - // foo (1).lng -> foo.lng - conflictingFile.moveTo(canonicalFile); - return canonicalFile; - } - } - - private static String createConflictId() { - return UUID.randomUUID().toString().substring(0, UUID_FIRST_GROUP_STRLEN); - } - -} diff --git a/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/FilenameShortener.java b/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/FilenameShortener.java deleted file mode 100644 index 557e399eb..000000000 --- a/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/FilenameShortener.java +++ /dev/null @@ -1,101 +0,0 @@ -/******************************************************************************* - * 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.shortening; - -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -import org.apache.commons.codec.binary.Base32; -import org.apache.commons.codec.binary.BaseNCodec; -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.io.FileContents; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -class FilenameShortener { - - private static final Logger LOG = LoggerFactory.getLogger(FilenameShortener.class); - private static final String LONG_NAME_FILE_EXT = ".lng"; - private static final ThreadLocal SHA1 = new ThreadLocalSha1(); - private static final BaseNCodec BASE32 = new Base32(); - private final Folder metadataRoot; - private final int threshold; - - public FilenameShortener(Folder metadataRoot, int threshold) { - this.metadataRoot = metadataRoot; - this.threshold = threshold; - } - - public String inflate(String shortName) { - if (shortName.endsWith(LONG_NAME_FILE_EXT)) { - return loadMapping(shortName); - } else { - return shortName; - } - } - - public String deflate(String longName) { - if (longName.length() < threshold) { - return longName; - } else { - final byte[] hashBytes = SHA1.get().digest(longName.getBytes(StandardCharsets.UTF_8)); - final String hash = BASE32.encodeAsString(hashBytes); - return hash + LONG_NAME_FILE_EXT; - } - } - - public boolean isShortened(String name) { - return name.endsWith(LONG_NAME_FILE_EXT); - } - - public void saveMapping(String longName, String shortName) { - final File mappingFile = mappingFile(shortName); - if (!mappingFile.exists()) { - mappingFile.parent().get().create(); - FileContents.UTF_8.writeContents(mappingFile, longName); - } - } - - private File mappingFile(String deflated) { - final Folder folder = metadataRoot.folder(deflated.substring(0, 2)).folder(deflated.substring(2, 4)); - return folder.file(deflated); - } - - private String loadMapping(String shortName) { - final File mappingFile = mappingFile(shortName); - if (!mappingFile.exists()) { - LOG.warn("Mapping file not found: " + mappingFile); - return shortName; - } else { - return FileContents.UTF_8.readContents(mappingFile); - } - } - - private static class ThreadLocalSha1 extends ThreadLocal { - - @Override - protected MessageDigest initialValue() { - try { - return MessageDigest.getInstance("SHA-1"); - } catch (NoSuchAlgorithmException e) { - throw new AssertionError("SHA-1 exists in every JVM"); - } - } - - @Override - public MessageDigest get() { - final MessageDigest messageDigest = super.get(); - messageDigest.reset(); - return messageDigest; - } - } - -} diff --git a/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/ShorteningFile.java b/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/ShorteningFile.java deleted file mode 100644 index 919a2a8a4..000000000 --- a/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/ShorteningFile.java +++ /dev/null @@ -1,60 +0,0 @@ -/******************************************************************************* - * 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.shortening; - -import java.io.UncheckedIOException; -import java.util.concurrent.atomic.AtomicReference; - -import org.cryptomator.common.LazyInitializer; -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.WritableFile; -import org.cryptomator.filesystem.delegating.DelegatingFile; - -class ShorteningFile extends DelegatingFile { - - private final AtomicReference longName; - private final FilenameShortener shortener; - - public ShorteningFile(ShorteningFolder parent, File delegate, String name, FilenameShortener shortener) { - super(parent, ConflictResolver.resolveConflictIfNecessary(delegate, shortener)); - this.longName = new AtomicReference<>(name); - this.shortener = shortener; - } - - @Override - public String name() throws UncheckedIOException { - return LazyInitializer.initializeLazily(longName, () -> { - return shortener.inflate(shortenedName()); - }); - } - - private String shortenedName() { - return delegate.name(); - } - - @Override - public WritableFile openWritable() throws UncheckedIOException { - if (shortener.isShortened(shortenedName())) { - shortener.saveMapping(name(), shortenedName()); - } - return super.openWritable(); - } - - @Override - public void moveTo(File destination) { - super.moveTo(destination); - if (destination instanceof ShorteningFile) { - ShorteningFile dest = (ShorteningFile) destination; - if (shortener.isShortened(dest.shortenedName())) { - shortener.saveMapping(dest.name(), dest.shortenedName()); - } - } - } - -} diff --git a/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/ShorteningFileSystem.java b/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/ShorteningFileSystem.java deleted file mode 100644 index 9dc6cbfa9..000000000 --- a/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/ShorteningFileSystem.java +++ /dev/null @@ -1,59 +0,0 @@ -/******************************************************************************* - * 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.shortening; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.util.stream.Stream; - -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.Node; -import org.cryptomator.filesystem.delegating.DelegatingFileSystem; - -public class ShorteningFileSystem extends ShorteningFolder implements DelegatingFileSystem { - - private final String metadataFolderName; - - public ShorteningFileSystem(Folder root, String metadataFolderName, int threshold) { - super(null, root, "", new FilenameShortener(root.resolveFolder(metadataFolderName), threshold)); - this.metadataFolderName = metadataFolderName; - create(); - } - - @Override - public Stream folders() { - return super.folders().filter(this::nameIsNotNameOfMetadataFolder); - } - - @Override - public ShorteningFile file(String name) throws UncheckedIOException { - if (metadataFolderName.equals(name)) { - throw new UncheckedIOException(new IOException("'" + name + "' is a reserved name.")); - } - return super.file(name); - } - - @Override - public ShorteningFolder folder(String name) throws UncheckedIOException { - if (metadataFolderName.equals(name)) { - throw new UncheckedIOException(new IOException("'" + name + "' is a reserved name.")); - } - return super.folder(name); - } - - private boolean nameIsNotNameOfMetadataFolder(Node node) { - return !metadataFolderName.equals(node.name()); - } - - @Override - public Folder getDelegate() { - return delegate; - } - -} diff --git a/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/ShorteningFileSystemFactory.java b/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/ShorteningFileSystemFactory.java deleted file mode 100644 index 02970d53b..000000000 --- a/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/ShorteningFileSystemFactory.java +++ /dev/null @@ -1,30 +0,0 @@ -/******************************************************************************* - * 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.shortening; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.Folder; - -@Singleton -public class ShorteningFileSystemFactory { - - private static final int SHORTENING_THRESHOLD = 129; // 128 + "_" - private static final String METADATA_FOLDER_NAME = "m"; - - @Inject - public ShorteningFileSystemFactory() { - } - - public FileSystem get(Folder root) { - return new ShorteningFileSystem(root, METADATA_FOLDER_NAME, SHORTENING_THRESHOLD); - } -} diff --git a/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/ShorteningFolder.java b/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/ShorteningFolder.java deleted file mode 100644 index dd9d5a64c..000000000 --- a/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/ShorteningFolder.java +++ /dev/null @@ -1,84 +0,0 @@ -/******************************************************************************* - * 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.shortening; - -import java.io.UncheckedIOException; -import java.util.concurrent.atomic.AtomicReference; - -import org.cryptomator.common.LazyInitializer; -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.delegating.DelegatingFolder; - -class ShorteningFolder extends DelegatingFolder { - - private final AtomicReference longName; - private final FilenameShortener shortener; - - public ShorteningFolder(ShorteningFolder parent, Folder delegate, String name, FilenameShortener shortener) { - super(parent, delegate); - this.longName = new AtomicReference<>(name); - this.shortener = shortener; - } - - @Override - public String name() throws UncheckedIOException { - return LazyInitializer.initializeLazily(longName, () -> { - return shortener.inflate(shortenedName()); - }); - } - - private String shortenedName() { - return delegate.name(); - } - - @Override - public ShorteningFile file(String name) throws UncheckedIOException { - return new ShorteningFile(this, delegate.file(shortener.deflate(name)), name, shortener); - } - - @Override - public ShorteningFolder folder(String name) throws UncheckedIOException { - return new ShorteningFolder(this, delegate.folder(shortener.deflate(name)), name, shortener); - } - - @Override - protected ShorteningFile newFile(File delegate) { - return new ShorteningFile(this, delegate, null, shortener); - } - - @Override - protected ShorteningFolder newFolder(Folder delegate) { - return new ShorteningFolder(this, delegate, null, shortener); - } - - @Override - public void create() throws UncheckedIOException { - if (exists()) { - return; - } - parent().ifPresent(Folder::create); - if (shortener.isShortened(shortenedName())) { - shortener.saveMapping(name(), shortenedName()); - } - super.create(); - } - - @Override - public void moveTo(Folder destination) { - super.moveTo(destination); - if (destination instanceof ShorteningFolder) { - ShorteningFolder dest = (ShorteningFolder) destination; - if (shortener.isShortened(dest.shortenedName())) { - shortener.saveMapping(dest.name(), dest.shortenedName()); - } - } - } - -} diff --git a/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/package-info.java b/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/package-info.java deleted file mode 100644 index 11b791fd2..000000000 --- a/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/package-info.java +++ /dev/null @@ -1,14 +0,0 @@ -/******************************************************************************* - * 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 - *******************************************************************************/ -/** - * Provides a decoration layer for the {@link org.cryptomator.filesystem Filesystem API}. - * {@link org.cryptomator.filesystem.File File} and {@link org.cryptomator.filesystem.Folder Folder} names exceeding a certain length limit will be mapped to shorter equivalents. - * The mapping itself is stored in metadata files inside the m/ directory on root level. - */ -package org.cryptomator.filesystem.shortening; \ No newline at end of file diff --git a/main/filesystem-nameshortening/src/test/java/org/cryptomator/filesystem/shortening/ConflictResolverTest.java b/main/filesystem-nameshortening/src/test/java/org/cryptomator/filesystem/shortening/ConflictResolverTest.java deleted file mode 100644 index 51d7c2015..000000000 --- a/main/filesystem-nameshortening/src/test/java/org/cryptomator/filesystem/shortening/ConflictResolverTest.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.cryptomator.filesystem.shortening; - -import java.util.Optional; - -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.inmem.InMemoryFileSystem; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; - -public class ConflictResolverTest { - - private Folder metadataFolder; - private FilenameShortener shortener; - private Folder folder; - private File canonicalFile; - private File conflictingFile; - private File resolvedFile; - - @Before - public void setup() { - metadataFolder = new InMemoryFileSystem(); - shortener = new FilenameShortener(metadataFolder, 20); - folder = Mockito.mock(Folder.class); - canonicalFile = Mockito.mock(File.class); - conflictingFile = Mockito.mock(File.class); - resolvedFile = Mockito.mock(File.class); - - String longName = "hello world, I am a very long file name. certainly longer than twenty characters.exe"; - String shortName = shortener.deflate(longName); - shortener.saveMapping(longName, shortName); - String canonicalFileName = shortName; - String conflictingFileName = shortName.replace(".lng", " (1).lng"); - - Mockito.when(canonicalFile.name()).thenReturn(canonicalFileName); - Mockito.when(conflictingFile.name()).thenReturn(conflictingFileName); - - Mockito.when(canonicalFile.exists()).thenReturn(true); - Mockito.when(conflictingFile.exists()).thenReturn(true); - - Mockito.doReturn(Optional.of(folder)).when(canonicalFile).parent(); - Mockito.doReturn(Optional.of(folder)).when(conflictingFile).parent(); - - Mockito.when(folder.file(Mockito.anyString())).thenReturn(resolvedFile); - Mockito.when(folder.file(canonicalFileName)).thenReturn(canonicalFile); - Mockito.when(folder.file(conflictingFileName)).thenReturn(conflictingFile); - } - - @Test - public void testNoConflictToBeResolved() { - File resolved = ConflictResolver.resolveConflictIfNecessary(canonicalFile, new FilenameShortener(metadataFolder, 20)); - Mockito.verify(conflictingFile, Mockito.never()).moveTo(Mockito.any()); - Assert.assertSame(canonicalFile, resolved); - } - - @Test - public void testConflictToBeResolved() { - File resolved = ConflictResolver.resolveConflictIfNecessary(conflictingFile, new FilenameShortener(metadataFolder, 20)); - Mockito.verify(conflictingFile).moveTo(resolvedFile); - Assert.assertSame(resolvedFile, resolved); - } - -} diff --git a/main/filesystem-nameshortening/src/test/java/org/cryptomator/filesystem/shortening/FilenameShortenerTest.java b/main/filesystem-nameshortening/src/test/java/org/cryptomator/filesystem/shortening/FilenameShortenerTest.java deleted file mode 100644 index faab82718..000000000 --- a/main/filesystem-nameshortening/src/test/java/org/cryptomator/filesystem/shortening/FilenameShortenerTest.java +++ /dev/null @@ -1,54 +0,0 @@ -/******************************************************************************* - * 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.shortening; - -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.inmem.InMemoryFileSystem; -import org.junit.Assert; -import org.junit.Test; - -public class FilenameShortenerTest { - - @Test - public void testNoDeflationOfShortFiles() { - FileSystem fs = new InMemoryFileSystem(); - FilenameShortener shortener = new FilenameShortener(fs, 10); - Assert.assertEquals("short", shortener.deflate("short")); - } - - @Test - public void testDeflateAndInflate() { - FileSystem fs = new InMemoryFileSystem(); - FilenameShortener shortener = new FilenameShortener(fs, 10); - - String longName = "suchALongName"; - String shortenedName = shortener.deflate(longName); - shortener.saveMapping(longName, shortenedName); - Assert.assertNotEquals(longName, shortenedName); - - Assert.assertEquals(longName, shortener.inflate(shortenedName)); - } - - @Test - public void testNoInflationOfShortFiles() { - FileSystem fs = new InMemoryFileSystem(); - FilenameShortener shortener = new FilenameShortener(fs, 10); - - Assert.assertEquals("short", shortener.inflate("short")); - } - - @Test - public void testInflateWithoutMappingFile() { - FileSystem fs = new InMemoryFileSystem(); - FilenameShortener shortener = new FilenameShortener(fs, 10); - - Assert.assertEquals("iJustMadeThisNameUp.lng", shortener.inflate("iJustMadeThisNameUp.lng")); - } - -} diff --git a/main/filesystem-nameshortening/src/test/java/org/cryptomator/filesystem/shortening/ShorteningFileSystemFactoryTest.java b/main/filesystem-nameshortening/src/test/java/org/cryptomator/filesystem/shortening/ShorteningFileSystemFactoryTest.java deleted file mode 100644 index fc3694b86..000000000 --- a/main/filesystem-nameshortening/src/test/java/org/cryptomator/filesystem/shortening/ShorteningFileSystemFactoryTest.java +++ /dev/null @@ -1,67 +0,0 @@ -/******************************************************************************* - * 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.shortening; - -import java.nio.ByteBuffer; - -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.WritableFile; -import org.cryptomator.filesystem.inmem.InMemoryFileSystem; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -public class ShorteningFileSystemFactoryTest { - - private static final ShorteningFileSystemFactory shorteningFsFactory = DaggerShorteningFileSystemTestComponent.create().shorteningFileSystemFactory(); - private static final String LONG_NAME = "aaaaabbbbbcccccdddddeeeeefffffggggghhhhhiiiiijjjjjkkkkklllll" // - + "mmmmmnnnnnooooopppppqqqqqrrrrrssssstttttuuuuuvvvvvwwwwwxxxxxyyyyyzzzzz" // - + "00000111112222233333444445555566666777778888899999"; - private static final String SHORTENED_FILE = "UYPJJ35VGP2JJ4YISC5S2XQLENLR5MVC.lng"; // base32(sha1(LONG_NAME)) + '.lng' - private static final String CORRESPONDING_METADATA_FILE = "m/UY/PJ/UYPJJ35VGP2JJ4YISC5S2XQLENLR5MVC.lng"; - - private FileSystem physicalFs; - private FileSystem shortenedFs; - - @Before - public void setupFileSystems() { - physicalFs = new InMemoryFileSystem(); - shortenedFs = shorteningFsFactory.get(physicalFs); - shortenedFs.create(); - } - - @Test - public void testFileCreation() { - File file = shortenedFs.file(LONG_NAME); - Assert.assertFalse(file.exists()); - Assert.assertFalse(physicalFs.resolveFile(SHORTENED_FILE).exists()); - Assert.assertFalse(physicalFs.resolveFile(CORRESPONDING_METADATA_FILE).exists()); - try (WritableFile w = file.openWritable()) { - w.write(ByteBuffer.allocate(0)); - } - Assert.assertTrue(file.exists()); - Assert.assertTrue(physicalFs.resolveFile(SHORTENED_FILE).exists()); - Assert.assertTrue(physicalFs.resolveFile(CORRESPONDING_METADATA_FILE).exists()); - } - - @Test - public void testFolderCreation() { - Folder folder = shortenedFs.folder(LONG_NAME); - Assert.assertFalse(folder.exists()); - Assert.assertFalse(physicalFs.resolveFolder(SHORTENED_FILE).exists()); - Assert.assertFalse(physicalFs.resolveFile(CORRESPONDING_METADATA_FILE).exists()); - folder.create(); - Assert.assertTrue(folder.exists()); - Assert.assertTrue(physicalFs.resolveFolder(SHORTENED_FILE).exists()); - Assert.assertTrue(physicalFs.resolveFile(CORRESPONDING_METADATA_FILE).exists()); - } - -} diff --git a/main/filesystem-nameshortening/src/test/java/org/cryptomator/filesystem/shortening/ShorteningFileSystemTest.java b/main/filesystem-nameshortening/src/test/java/org/cryptomator/filesystem/shortening/ShorteningFileSystemTest.java deleted file mode 100644 index b4dad258c..000000000 --- a/main/filesystem-nameshortening/src/test/java/org/cryptomator/filesystem/shortening/ShorteningFileSystemTest.java +++ /dev/null @@ -1,238 +0,0 @@ -/******************************************************************************* - * 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.shortening; - -import static java.lang.String.format; -import static java.util.stream.Collectors.toList; -import static org.cryptomator.common.test.matcher.ContainsMatcher.contains; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; - -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.util.concurrent.TimeoutException; - -import org.cryptomator.common.test.matcher.PropertyMatcher; -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.filesystem.WritableFile; -import org.cryptomator.filesystem.inmem.InMemoryFileSystem; -import org.hamcrest.Matcher; -import org.junit.Assert; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -public class ShorteningFileSystemTest { - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private static final String METADATA_DIR_NAME = "m"; - private static final int THRESHOLD = 10; - private static final String NAME_LONGER_THAN_THRESHOLD = "morethantenchars"; - - @Test - public void testImplicitCreationOfMetadataFolder() { - final FileSystem underlyingFs = new InMemoryFileSystem(); - final Folder metadataRoot = underlyingFs.folder(METADATA_DIR_NAME); - final FileSystem fs = new ShorteningFileSystem(underlyingFs, METADATA_DIR_NAME, THRESHOLD); - fs.folder(NAME_LONGER_THAN_THRESHOLD).create(); - Assert.assertTrue(metadataRoot.exists()); - } - - @Test - public void testMetadataFolderIsNotIncludedInFolderListing() { - final FileSystem underlyingFs = new InMemoryFileSystem(); - final FileSystem fs = new ShorteningFileSystem(underlyingFs, METADATA_DIR_NAME, THRESHOLD); - fs.folder(NAME_LONGER_THAN_THRESHOLD).create(); - - assertThat(fs.folders().collect(toList()), contains(folderWithName(NAME_LONGER_THAN_THRESHOLD))); - } - - @Test - public void testMetadataFolderIsNotIncludedInChildrenListing() { - final FileSystem underlyingFs = new InMemoryFileSystem(); - final FileSystem fs = new ShorteningFileSystem(underlyingFs, METADATA_DIR_NAME, THRESHOLD); - fs.folder(NAME_LONGER_THAN_THRESHOLD).create(); - - assertThat(fs.children().collect(toList()), contains(folderWithName(NAME_LONGER_THAN_THRESHOLD))); - } - - @Test - public void testCanNotObtainFolderWithNameOfMetadataFolder() { - final FileSystem underlyingFs = new InMemoryFileSystem(); - final FileSystem fs = new ShorteningFileSystem(underlyingFs, METADATA_DIR_NAME, THRESHOLD); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(format("'%s' is a reserved name", METADATA_DIR_NAME)); - - fs.folder(METADATA_DIR_NAME); - } - - @Test - public void testCanNotObtainFileWithNameOfMetadataFolder() { - final FileSystem underlyingFs = new InMemoryFileSystem(); - final FileSystem fs = new ShorteningFileSystem(underlyingFs, METADATA_DIR_NAME, THRESHOLD); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(format("'%s' is a reserved name", METADATA_DIR_NAME)); - - fs.file(METADATA_DIR_NAME); - } - - @Test - public void testDeflate() { - final FileSystem underlyingFs = new InMemoryFileSystem(); - final Folder metadataRoot = underlyingFs.folder(METADATA_DIR_NAME); - final FileSystem fs = new ShorteningFileSystem(underlyingFs, METADATA_DIR_NAME, 10); - final Folder longNamedFolder = fs.folder("morethantenchars"); // base32(sha1(morethantenchars)) = QMJL5GQUETRX2YRV6XDTJQ6NNM7IEUHP - final File correspondingMetadataFile = metadataRoot.folder("QM").folder("JL").file("QMJL5GQUETRX2YRV6XDTJQ6NNM7IEUHP.lng"); - longNamedFolder.create(); - Assert.assertTrue(longNamedFolder.exists()); - Assert.assertTrue(correspondingMetadataFile.exists()); - } - - @Test - public void testInflate() { - final FileSystem underlyingFs = new InMemoryFileSystem(); - final Folder metadataRoot = underlyingFs.folder(METADATA_DIR_NAME); - final FileSystem fs = new ShorteningFileSystem(underlyingFs, METADATA_DIR_NAME, 10); - final File correspondingMetadataFile = metadataRoot.folder("QM").folder("JL").file("QMJL5GQUETRX2YRV6XDTJQ6NNM7IEUHP.lng"); - final Folder shortenedFolder = underlyingFs.folder("QMJL5GQUETRX2YRV6XDTJQ6NNM7IEUHP.lng"); - shortenedFolder.create(); - correspondingMetadataFile.parent().get().create(); - try (WritableFile w = correspondingMetadataFile.openWritable()) { - w.write(ByteBuffer.wrap("morethantenchars".getBytes(StandardCharsets.UTF_8))); - } - Assert.assertTrue(correspondingMetadataFile.exists()); - Assert.assertTrue(fs.folders().map(Folder::name).anyMatch(n -> n.equals("morethantenchars"))); - } - - @Test - public void testInflateFailedDueToMissingMapping() { - final FileSystem underlyingFs = new InMemoryFileSystem(); - final FileSystem fs = new ShorteningFileSystem(underlyingFs, METADATA_DIR_NAME, 10); - final Folder shortenedFolder = underlyingFs.folder("QMJL5GQUETRX2YRV6XDTJQ6NNM7IEUHP.lng"); - shortenedFolder.create(); - Assert.assertTrue(fs.folders().map(Folder::name).anyMatch(n -> n.equals("QMJL5GQUETRX2YRV6XDTJQ6NNM7IEUHP.lng"))); - } - - @Test - public void testMoveLongFolders() { - final FileSystem underlyingFs = new InMemoryFileSystem(); - final Folder metadataRoot = underlyingFs.folder(METADATA_DIR_NAME); - metadataRoot.create(); - final FileSystem fs = new ShorteningFileSystem(underlyingFs, METADATA_DIR_NAME, THRESHOLD); - - final Folder shortNamedFolder = fs.folder("test"); - shortNamedFolder.create(); - Assert.assertFalse(metadataRoot.children().findAny().isPresent()); - - final Folder longNamedFolder = fs.folder("morethantenchars"); - shortNamedFolder.moveTo(longNamedFolder); - Assert.assertTrue(metadataRoot.children().findAny().isPresent()); - } - - @Test - public void testMoveLongFiles() throws UncheckedIOException, TimeoutException { - final FileSystem underlyingFs = new InMemoryFileSystem(); - final Folder metadataRoot = underlyingFs.folder(METADATA_DIR_NAME); - metadataRoot.create(); - final FileSystem fs = new ShorteningFileSystem(underlyingFs, METADATA_DIR_NAME, THRESHOLD); - - final File shortNamedFolder = fs.file("test"); - try (WritableFile file = shortNamedFolder.openWritable()) { - file.write(ByteBuffer.wrap("hello world".getBytes())); - } - Assert.assertFalse(metadataRoot.children().findAny().isPresent()); - - final File longNamedFolder = fs.file("morethantenchars"); - shortNamedFolder.moveTo(longNamedFolder); - Assert.assertTrue(metadataRoot.children().findAny().isPresent()); - } - - @Test - public void testDeflateAndInflateFolder() { - final FileSystem underlyingFs = new InMemoryFileSystem(); - final FileSystem fs1 = new ShorteningFileSystem(underlyingFs, METADATA_DIR_NAME, THRESHOLD); - final Folder longNamedFolder1 = fs1.folder("morethantenchars"); - longNamedFolder1.create(); - - final FileSystem fs2 = new ShorteningFileSystem(underlyingFs, METADATA_DIR_NAME, THRESHOLD); - final Folder longNamedFolder2 = fs2.folder("morethantenchars"); - Assert.assertTrue(longNamedFolder2.exists()); - } - - @Test - public void testDeflateAndInflateFolderAndFile() throws UncheckedIOException, TimeoutException { - final FileSystem underlyingFs = new InMemoryFileSystem(); - - // write: - final FileSystem fs1 = new ShorteningFileSystem(underlyingFs, METADATA_DIR_NAME, THRESHOLD); - fs1.folder("morethantenchars").create(); - try (WritableFile file = fs1.folder("morethantenchars").file("morethanelevenchars.txt").openWritable()) { - file.write(ByteBuffer.wrap("hello world".getBytes())); - } - - // read - final FileSystem fs2 = new ShorteningFileSystem(underlyingFs, METADATA_DIR_NAME, THRESHOLD); - try (ReadableFile file = fs2.folder("morethantenchars").file("morethanelevenchars.txt").openReadable()) { - ByteBuffer buf = ByteBuffer.allocate(11); - file.read(buf); - Assert.assertEquals("hello world", new String(buf.array())); - } - } - - @Test - public void testPassthroughShortNamedFiles() throws UncheckedIOException, TimeoutException, InterruptedException { - final FileSystem underlyingFs = new InMemoryFileSystem(); - final FileSystem fs = new ShorteningFileSystem(underlyingFs, METADATA_DIR_NAME, THRESHOLD); - - final Instant testStart = Instant.now(); - - Thread.sleep(1); - - // of folders: - underlyingFs.folder("foo").folder("bar").create(); - Assert.assertTrue(fs.folder("foo").folder("bar").exists()); - - // from underlying: - try (WritableFile file = underlyingFs.folder("foo").file("test1.txt").openWritable()) { - file.write(ByteBuffer.wrap("hello world".getBytes())); - } - try (ReadableFile file = fs.folder("foo").file("test1.txt").openReadable()) { - ByteBuffer buf = ByteBuffer.allocate(11); - file.read(buf); - buf.flip(); - Assert.assertEquals("hello world", new String(buf.array())); - } - Assert.assertTrue(fs.folder("foo").file("test1.txt").lastModified().isAfter(testStart)); - - // to underlying: - try (WritableFile file = fs.folder("foo").file("test2.txt").openWritable()) { - file.write(ByteBuffer.wrap("hello world".getBytes())); - } - try (ReadableFile file = underlyingFs.folder("foo").file("test2.txt").openReadable()) { - ByteBuffer buf = ByteBuffer.allocate(11); - file.read(buf); - Assert.assertEquals("hello world", new String(buf.array())); - } - Assert.assertTrue(fs.folder("foo").file("test2.txt").lastModified().isAfter(testStart)); - } - - public static Matcher folderWithName(String name) { - return new PropertyMatcher<>(Folder.class, Folder::name, "name", is(name)); - } - -} diff --git a/main/filesystem-nameshortening/src/test/java/org/cryptomator/filesystem/shortening/ShorteningFileSystemTestComponent.java b/main/filesystem-nameshortening/src/test/java/org/cryptomator/filesystem/shortening/ShorteningFileSystemTestComponent.java deleted file mode 100644 index 89a36026f..000000000 --- a/main/filesystem-nameshortening/src/test/java/org/cryptomator/filesystem/shortening/ShorteningFileSystemTestComponent.java +++ /dev/null @@ -1,21 +0,0 @@ -/******************************************************************************* - * 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.shortening; - -import javax.inject.Singleton; - -import dagger.Component; - -@Singleton -@Component -public interface ShorteningFileSystemTestComponent { - - ShorteningFileSystemFactory shorteningFileSystemFactory(); - -} diff --git a/main/filesystem-nameshortening/src/test/resources/log4j2.xml b/main/filesystem-nameshortening/src/test/resources/log4j2.xml deleted file mode 100644 index 9b4889392..000000000 --- a/main/filesystem-nameshortening/src/test/resources/log4j2.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/main/filesystem-nio/.gitignore b/main/filesystem-nio/.gitignore deleted file mode 100644 index b83d22266..000000000 --- a/main/filesystem-nio/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target/ diff --git a/main/filesystem-nio/pom.xml b/main/filesystem-nio/pom.xml deleted file mode 100644 index 80c9dea6e..000000000 --- a/main/filesystem-nio/pom.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - - 4.0.0 - - org.cryptomator - main - 1.3.0-SNAPSHOT - - filesystem-nio - Cryptomator filesystem: NIO-based physical layer - FileSystem implementation to access the real file system of an operating system - - - - org.cryptomator - commons - - - org.cryptomator - filesystem-api - - - - commons-io - commons-io - - - org.apache.commons - commons-lang3 - - - org.apache.commons - commons-collections4 - - - - com.google.guava - guava - - - - org.cryptomator - commons - - - - - org.cryptomator - commons-test - - - - - - - org.jacoco - jacoco-maven-plugin - - - - \ No newline at end of file diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/DefaultInstanceFactory.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/DefaultInstanceFactory.java deleted file mode 100644 index 3b450a21a..000000000 --- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/DefaultInstanceFactory.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.cryptomator.filesystem.nio; - -import java.nio.file.Path; -import java.util.Optional; - -import org.cryptomator.filesystem.FileSystem; - -class DefaultInstanceFactory implements InstanceFactory { - - @Override - public NioFolder nioFolder(Optional parent, Path path, NioAccess nioAccess) { - return new NioFolder(parent, path, nioAccess, this); - } - - @Override - public NioFile nioFile(Optional parent, Path path, NioAccess nioAccess) { - return new NioFile(parent, path, nioAccess, this); - } - - @Override - public SharedFileChannel sharedFileChannel(Path path, NioAccess nioAccess) { - return new SharedFileChannel(path, nioAccess); - } - - @Override - public WritableNioFile writableNioFile(FileSystem fileSystem, Path path, SharedFileChannel channel, Runnable afterCloseCallback) { - return new WritableNioFile(fileSystem, path, channel, afterCloseCallback); - } - - @Override - public ReadableNioFile readableNioFile(Path path, SharedFileChannel channel, Runnable afterCloseCallback) { - return new ReadableNioFile(path, channel, afterCloseCallback); - } - -} diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/DefaultNioAccess.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/DefaultNioAccess.java deleted file mode 100644 index 5def2b008..000000000 --- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/DefaultNioAccess.java +++ /dev/null @@ -1,106 +0,0 @@ -package org.cryptomator.filesystem.nio; - -import java.io.IOException; -import java.nio.channels.AsynchronousFileChannel; -import java.nio.file.AccessDeniedException; -import java.nio.file.CopyOption; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.LinkOption; -import java.nio.file.OpenOption; -import java.nio.file.Path; -import java.nio.file.attribute.BasicFileAttributeView; -import java.nio.file.attribute.BasicFileAttributes; -import java.nio.file.attribute.FileAttribute; -import java.nio.file.attribute.FileTime; -import java.util.stream.Stream; - -class DefaultNioAccess implements NioAccess { - - @Override - public long size(Path path) throws IOException { - return Files.size(path); - } - - @Override - public AsynchronousFileChannel open(Path path, OpenOption... options) throws IOException { - return AsynchronousFileChannel.open(path, options); - } - - @Override - public boolean isRegularFile(Path path, LinkOption... options) { - return Files.isRegularFile(path, options); - } - - @Override - public boolean exists(Path path, LinkOption... options) { - return Files.exists(path, options); - } - - @Override - public boolean isDirectory(Path path, LinkOption... options) { - return Files.isDirectory(path, options); - } - - @Override - public Stream list(Path dir) throws IOException { - return Files.list(dir); - } - - @Override - public void createDirectories(Path dir, FileAttribute... attrs) throws IOException { - Files.createDirectories(dir, attrs); - } - - @Override - public FileTime getLastModifiedTime(Path path, LinkOption... options) throws IOException { - return Files.getLastModifiedTime(path, options); - } - - @Override - public void delete(Path path) throws IOException { - try { - Files.delete(path); - } catch (AccessDeniedException e) { - // workaround for https://github.com/cryptomator/cryptomator/issues/317 - try { - if (path.toFile().delete()) - return; - } catch (UnsupportedOperationException e2) { - // ignore - } - throw e; - } - } - - @Override - public void close(AsynchronousFileChannel channel) throws IOException { - channel.close(); - } - - @Override - public void move(Path source, Path target, CopyOption... options) throws IOException { - Files.move(source, target, options); - } - - @Override - public void setLastModifiedTime(Path path, FileTime time) throws IOException { - Files.setLastModifiedTime(path, time); - } - - @Override - public String separator() { - return FileSystems.getDefault().getSeparator(); - } - - @Override - public FileTime getCreationTime(Path path, LinkOption... options) throws IOException { - return Files.readAttributes(path, BasicFileAttributes.class, options).creationTime(); - } - - @Override - public void setCreationTime(Path path, FileTime creationTime, LinkOption... options) throws IOException { - Files.getFileAttributeView(path, BasicFileAttributeView.class, options).setTimes(null, null, creationTime); - } - -} diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/InstanceFactory.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/InstanceFactory.java deleted file mode 100644 index 4a5fee15a..000000000 --- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/InstanceFactory.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.cryptomator.filesystem.nio; - -import java.nio.file.Path; -import java.util.Optional; - -import org.cryptomator.common.Holder; -import org.cryptomator.filesystem.FileSystem; - -interface InstanceFactory { - - public static final Holder DEFAULT = new Holder<>(new DefaultInstanceFactory()); - - NioFolder nioFolder(Optional parent, Path path, NioAccess nioAccess); - - NioFile nioFile(Optional parent, Path path, NioAccess nioAccess); - - SharedFileChannel sharedFileChannel(Path path, NioAccess nioAccess); - - WritableNioFile writableNioFile(FileSystem fileSystem, Path path, SharedFileChannel channel, Runnable afterCloseCallback); - - ReadableNioFile readableNioFile(Path path, SharedFileChannel channel, Runnable afterCloseCallback); - -} diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioAccess.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioAccess.java deleted file mode 100644 index f19c46a18..000000000 --- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioAccess.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.cryptomator.filesystem.nio; - -import java.io.IOException; -import java.nio.channels.AsynchronousFileChannel; -import java.nio.file.CopyOption; -import java.nio.file.LinkOption; -import java.nio.file.OpenOption; -import java.nio.file.Path; -import java.nio.file.attribute.FileAttribute; -import java.nio.file.attribute.FileTime; -import java.util.stream.Stream; - -import org.cryptomator.common.Holder; - -interface NioAccess { - - public static final Holder DEFAULT = new Holder<>(new DefaultNioAccess()); - - long size(Path path) throws IOException; - - AsynchronousFileChannel open(Path path, OpenOption... options) throws IOException; - - boolean isRegularFile(Path path, LinkOption... options); - - boolean exists(Path path, LinkOption... options); - - boolean isDirectory(Path childPath, LinkOption... options); - - Stream list(Path dir) throws IOException; - - void createDirectories(Path dir, FileAttribute... attrs) throws IOException; - - FileTime getLastModifiedTime(Path path, LinkOption... options) throws IOException; - - void delete(Path path) throws IOException; - - void close(AsynchronousFileChannel channel) throws IOException; - - void move(Path source, Path target, CopyOption... options) throws IOException; - - void setLastModifiedTime(Path path, FileTime time) throws IOException; - - String separator(); - - FileTime getCreationTime(Path path, LinkOption... options) throws IOException; - - void setCreationTime(Path path, FileTime creationTime, LinkOption... options) throws IOException; - -} diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFile.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFile.java deleted file mode 100644 index a82e0d289..000000000 --- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFile.java +++ /dev/null @@ -1,177 +0,0 @@ -package org.cryptomator.filesystem.nio; - -import static java.lang.String.format; -import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Path; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.filesystem.WritableFile; - -class NioFile extends NioNode implements File { - - private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true); - private final SharedFileChannel sharedChannel; - - public NioFile(Optional parent, Path eventuallyNonAbsolutePath, NioAccess nioAccess, InstanceFactory instanceFactory) { - super(parent, eventuallyNonAbsolutePath, nioAccess, instanceFactory); - sharedChannel = instanceFactory.sharedFileChannel(path, nioAccess); - } - - @Override - public long size() throws UncheckedIOException { - try { - return nioAccess.size(path); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - @Override - public ReadableFile openReadable() throws UncheckedIOException { - if (lock.getWriteHoldCount() > 0) { - throw new IllegalStateException("Current thread is currently writing " + path); - } - if (lock.getReadHoldCount() > 0) { - throw new IllegalStateException("Current thread is already reading " + path); - } - lock.readLock().lock(); - ReadableFile result = null; - try { - result = instanceFactory.readableNioFile(path, sharedChannel, this::unlockReadLock); - } finally { - if (result == null) { - unlockReadLock(); - } - } - return result; - } - - private void unlockReadLock() { - lock.readLock().unlock(); - } - - @Override - public WritableFile openWritable() throws UncheckedIOException { - if (lock.getWriteHoldCount() > 0) { - throw new IllegalStateException("Current thread is already writing " + path); - } - if (lock.getReadHoldCount() > 0) { - throw new IllegalStateException("Current thread is currently reading " + path); - } - lockWriteLock(); - WritableFile result = null; - try { - result = instanceFactory.writableNioFile(fileSystem(), path, sharedChannel, this::unlockWriteLock); - } finally { - if (result == null) { - unlockWriteLock(); - } - } - return result; - } - - // visible for testing - void lockWriteLock() { - lock.writeLock().lock(); - } - - // visible for testing - void unlockWriteLock() { - lock.writeLock().unlock(); - } - - @Override - public boolean exists() throws UncheckedIOException { - return nioAccess.isRegularFile(path); - } - - @Override - public Instant lastModified() throws UncheckedIOException { - if (nioAccess.exists(path) && !exists()) { - throw new UncheckedIOException(new IOException(format("%s is a folder", path))); - } - return super.lastModified(); - } - - @Override - public Optional creationTime() throws UncheckedIOException { - if (nioAccess.exists(path) && !exists()) { - throw new UncheckedIOException(new IOException(format("%s is a folder", path))); - } - return super.creationTime(); - } - - @Override - public void moveTo(File destination) throws UncheckedIOException { - if (destination == this) { - return; - } else if (belongsToSameFilesystem(destination)) { - internalMoveTo((NioFile) destination); - } else { - throw new IllegalArgumentException("Can only move to a File from the same FileSystem"); - } - } - - private void assertMovePreconditionsAreMet(NioFile destination) { - if (nioAccess.isDirectory(path())) { - throw new UncheckedIOException(new IOException(format("Can not move %s to %s. Source is a directory", path(), destination.path()))); - } - if (nioAccess.isDirectory(destination.path())) { - throw new UncheckedIOException(new IOException(format("Can not move %s to %s. Target is a directory", path(), destination.path()))); - } - } - - private void internalMoveTo(NioFile destination) { - assertMovePreconditionsAreMet(destination); - // TODO review deadlock-safety of locking two files. see DeadLockSafeFileOpener - List filesToBeLocked = new ArrayList<>(); - filesToBeLocked.add(this); - filesToBeLocked.add(destination); - Collections.sort(filesToBeLocked); - filesToBeLocked.forEach(file -> file.lockWriteLock()); - try { - nioAccess.move(path(), destination.path(), REPLACE_EXISTING); - } catch (IOException e) { - throw new UncheckedIOException(e); - } finally { - filesToBeLocked.forEach(file -> file.unlockWriteLock()); - } - } - - @Override - public void delete() throws UncheckedIOException { - if (!exists()) { - return; - } - try { - nioAccess.delete(path()); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - @Override - public int compareTo(File o) { - if (belongsToSameFilesystem(o)) { - return path.compareTo(((NioFile) o).path); - } else { - throw new IllegalArgumentException("Can not mix File objects from different file systems"); - } - } - - @Override - public String toString() { - return format("NioFile(%s)", path); - } - -} diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFileSystem.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFileSystem.java deleted file mode 100644 index a2b9863df..000000000 --- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFileSystem.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.cryptomator.filesystem.nio; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Optional; - -import org.cryptomator.filesystem.FileSystem; - -public class NioFileSystem extends NioFolder implements FileSystem { - - public static NioFileSystem rootedAt(Path root) { - return new NioFileSystem(root); - } - - private NioFileSystem(Path root) { - super(Optional.empty(), root, NioAccess.DEFAULT.get(), InstanceFactory.DEFAULT.get()); - create(); - } - - @Override - public Optional quotaUsedBytes() { - try { - long availableBytes = Files.getFileStore(path).getUsableSpace(); - long totalBytes = Files.getFileStore(path).getTotalSpace(); - return Optional.of(totalBytes - availableBytes); - } catch (IOException e) { - e.printStackTrace(); - return Optional.empty(); - } - } - - @Override - public Optional quotaAvailableBytes() { - try { - long availableBytes = Files.getFileStore(path).getUsableSpace(); - return Optional.of(availableBytes); - } catch (IOException e) { - e.printStackTrace(); - return Optional.empty(); - } - } - -} diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFolder.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFolder.java deleted file mode 100644 index cbc4c563d..000000000 --- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFolder.java +++ /dev/null @@ -1,142 +0,0 @@ -package org.cryptomator.filesystem.nio; - -import static java.lang.String.format; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Path; -import java.time.Instant; -import java.util.Optional; -import java.util.stream.Stream; - -import org.cryptomator.common.WeakValuedCache; -import org.cryptomator.common.streams.AutoClosingStream; -import org.cryptomator.filesystem.Deleter; -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.Node; - -class NioFolder extends NioNode implements Folder { - - private final WeakValuedCache folders = WeakValuedCache.usingLoader(this::folderFromPath); - private final WeakValuedCache files = WeakValuedCache.usingLoader(this::fileFromPath); - - public NioFolder(Optional parent, Path path, NioAccess nioAccess, InstanceFactory instanceFactory) { - super(parent, path, nioAccess, instanceFactory); - } - - @Override - public Stream children() throws UncheckedIOException { - try { - return AutoClosingStream.from(nioAccess.list(path).map(this::childPathToNode)); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - private NioNode childPathToNode(Path childPath) { - if (nioAccess.isDirectory(childPath)) { - return folders.get(childPath); - } else { - return files.get(childPath); - } - } - - private NioFile fileFromPath(Path path) { - return instanceFactory.nioFile(Optional.of(this), path, nioAccess); - } - - private NioFolder folderFromPath(Path path) { - return instanceFactory.nioFolder(Optional.of(this), path, nioAccess); - } - - @Override - public File file(String name) throws UncheckedIOException { - assertDoesNotContainsSeparator(name); - return files.get(path.resolve(name)); - } - - @Override - public Folder folder(String name) throws UncheckedIOException { - assertDoesNotContainsSeparator(name); - return folders.get(path.resolve(name)); - } - - private void assertDoesNotContainsSeparator(String name) { - if (name.contains(nioAccess.separator())) { - throw new IllegalArgumentException(format("Name must not contain file system separator (name: %s, separator: %s)", name, nioAccess.separator())); - } - } - - @Override - public void create() throws UncheckedIOException { - try { - nioAccess.createDirectories(path); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - @Override - public Instant lastModified() throws UncheckedIOException { - if (nioAccess.exists(path) && !nioAccess.isDirectory(path)) { - throw new UncheckedIOException(new IOException(format("%s is a file", path))); - } - return super.lastModified(); - } - - @Override - public boolean exists() throws UncheckedIOException { - return nioAccess.isDirectory(path); - } - - @Override - public void moveTo(Folder target) { - if (belongsToSameFilesystem(target)) { - internalMoveTo((NioFolder) target); - } else { - throw new IllegalArgumentException("Can only move a Folder to a Folder in the same FileSystem"); - } - } - - private void internalMoveTo(NioFolder target) { - try { - target.delete(); - target.parent().ifPresent(folder -> folder.create()); - nioAccess.move(path(), target.path()); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - Path path() { - return path; - } - - @Override - public Optional creationTime() throws UncheckedIOException { - if (nioAccess.exists(path) && !nioAccess.isDirectory(path)) { - throw new UncheckedIOException(new IOException(format("%s is a file", path))); - } - return super.creationTime(); - } - - @Override - public String toString() { - return format("NioFolder(%s)", path); - } - - @Override - public void delete() { - if (!exists()) { - return; - } - Deleter.deleteContent(this); - try { - nioAccess.delete(path); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - -} diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioNode.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioNode.java deleted file mode 100644 index 6a82c7af9..000000000 --- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioNode.java +++ /dev/null @@ -1,86 +0,0 @@ -package org.cryptomator.filesystem.nio; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Path; -import java.nio.file.attribute.FileTime; -import java.time.Instant; -import java.util.Optional; - -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.Node; - -abstract class NioNode implements Node { - - protected final Optional parent; - protected final Path path; - - protected final NioAccess nioAccess; - protected final InstanceFactory instanceFactory; - - public NioNode(Optional parent, Path path, NioAccess nioAccess, InstanceFactory instanceFactoy) { - this.path = path.toAbsolutePath(); - this.parent = parent; - this.nioAccess = nioAccess; - this.instanceFactory = instanceFactoy; - } - - // visible for testing - Path path() { - return path; - } - - @Override - public String name() throws UncheckedIOException { - return path.getFileName().toString(); - } - - @Override - public Optional parent() throws UncheckedIOException { - return parent; - } - - private static final Instant JANNUARY_THE_SECOND_NINTEENHUNDRED_SEVENTY = Instant.parse("1970-01-02T00:00:00Z"); - - @Override - public Instant lastModified() throws UncheckedIOException { - try { - return nioAccess.getLastModifiedTime(path).toInstant(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - @Override - public void setLastModified(Instant lastModified) throws UncheckedIOException { - try { - nioAccess.setLastModifiedTime(path, FileTime.from(lastModified)); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - @Override - public Optional creationTime() throws UncheckedIOException { - try { - Instant instant = nioAccess.getCreationTime(path).toInstant(); - if (instant.isBefore(JANNUARY_THE_SECOND_NINTEENHUNDRED_SEVENTY)) { - return Optional.empty(); - } else { - return Optional.of(instant); - } - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - @Override - public void setCreationTime(Instant creationTime) throws UncheckedIOException { - try { - nioAccess.setCreationTime(path, FileTime.from(creationTime)); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - -} diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/OpenCloseCounter.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/OpenCloseCounter.java deleted file mode 100644 index d0998768c..000000000 --- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/OpenCloseCounter.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.cryptomator.filesystem.nio; - -import java.util.concurrent.atomic.AtomicInteger; - -class OpenCloseCounter { - - private final AtomicInteger counter = new AtomicInteger(); - - public void countOpen() { - counter.incrementAndGet(); - } - - public void countClose() { - int openCount = counter.decrementAndGet(); - if (openCount < 0) { - counter.incrementAndGet(); - throw new IllegalStateException("Close without corresponding open"); - } - } - - public boolean isOpen() { - return counter.get() > 0; - } - -} diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/OpenMode.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/OpenMode.java deleted file mode 100644 index a0616839f..000000000 --- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/OpenMode.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.cryptomator.filesystem.nio; - -enum OpenMode { - READ, WRITE -} diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/ReadableNioFile.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/ReadableNioFile.java deleted file mode 100644 index b7bff19e9..000000000 --- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/ReadableNioFile.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.cryptomator.filesystem.nio; - -import static java.lang.String.format; -import static org.cryptomator.filesystem.nio.OpenMode.READ; - -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.nio.channels.ClosedChannelException; -import java.nio.file.Path; - -import org.cryptomator.filesystem.ReadableFile; - -class ReadableNioFile implements ReadableFile { - - private final Path path; - private final SharedFileChannel channel; - private final Runnable afterCloseCallback; - - private boolean open = true; - private long position = 0; - - public ReadableNioFile(Path path, SharedFileChannel channel, Runnable afterCloseCallback) { - this.path = path; - this.channel = channel; - this.afterCloseCallback = afterCloseCallback; - channel.open(READ); - } - - @Override - public int read(ByteBuffer target) throws UncheckedIOException { - assertOpen(); - int read = channel.readFully(position, target); - if (read != SharedFileChannel.EOF) { - position += read; - } - return read; - } - - @Override - public boolean isOpen() { - return open; - } - - @Override - public void position(long position) throws UncheckedIOException { - assertOpen(); - if (position < 0) { - throw new IllegalArgumentException(); - } - this.position = position; - } - - @Override - public void close() { - if (!open) { - return; - } - open = false; - try { - channel.close(); - } finally { - afterCloseCallback.run(); - } - } - - private void assertOpen() { - if (!open) { - throw new UncheckedIOException(format("%s already closed.", this), new ClosedChannelException()); - } - } - - @Override - public String toString() { - return format("ReadableNioFile(%s)", path); - } - -} \ No newline at end of file diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/SharedFileChannel.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/SharedFileChannel.java deleted file mode 100644 index 692e02833..000000000 --- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/SharedFileChannel.java +++ /dev/null @@ -1,172 +0,0 @@ -package org.cryptomator.filesystem.nio; - -import static java.lang.String.format; - -import java.io.IOException; -import java.io.InterruptedIOException; -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.nio.channels.AsynchronousFileChannel; -import java.nio.file.NoSuchFileException; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -class SharedFileChannel { - - public static final int EOF = -1; - - private final Path path; - private final NioAccess nioAccess; - private final OpenCloseCounter openCloseCounter; - - private Lock lock = new ReentrantLock(); - - private AsynchronousFileChannel delegate; - - public SharedFileChannel(Path path, NioAccess nioAccess) { - this.path = path; - this.nioAccess = nioAccess; - this.openCloseCounter = new OpenCloseCounter(); - } - - public void open(OpenMode mode) { - doLocked(() -> { - boolean failed = true; - try { - openCloseCounter.countOpen(); - if (delegate == null) { - createChannel(mode); - } - failed = false; - } finally { - if (failed) { - openCloseCounter.countClose(); - } - } - }); - } - - public void close() { - doLocked(() -> { - openCloseCounter.countClose(); - try { - delegate.force(true); - } catch (IOException e) { - throw new UncheckedIOException(e); - } finally { - if (!openCloseCounter.isOpen()) { - closeChannel(); - } - } - }); - } - - private void createChannel(OpenMode mode) { - try { - if (nioAccess.isDirectory(path)) { - throw new IOException(format("%s is a directory", path)); - } - if (mode == OpenMode.READ) { - if (!nioAccess.isRegularFile(path)) { - throw new NoSuchFileException(format("%s does not exist", path)); - } - } - delegate = nioAccess.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - private void closeChannel() { - try { - nioAccess.close(delegate); - } catch (IOException e) { - throw new UncheckedIOException(e); - } finally { - delegate = null; - } - } - - public int readFully(long position, ByteBuffer target) { - assertOpen(); - try { - return tryReadFully(position, target); - } catch (InterruptedException e) { - throw new UncheckedIOException(new InterruptedIOException("read has been interrupted")); - } catch (ExecutionException e) { - throw new UncheckedIOException(new IOException(e)); - } - } - - private int tryReadFully(long position, ByteBuffer target) throws InterruptedException, ExecutionException { - int initialRemaining = target.remaining(); - long maxPosition = position + initialRemaining; - do { - if (delegate.read(target, maxPosition - target.remaining()).get() == EOF) { - if (initialRemaining == target.remaining()) { - return EOF; - } else { - return initialRemaining - target.remaining(); - } - } - } while (target.hasRemaining()); - return initialRemaining - target.remaining(); - } - - public void truncate(int i) { - assertOpen(); - try { - delegate.truncate(i); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public long size() { - assertOpen(); - try { - return delegate.size(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - private void doLocked(Runnable task) { - lock.lock(); - try { - task.run(); - } finally { - lock.unlock(); - } - } - - public int writeFully(long position, ByteBuffer source) { - assertOpen(); - try { - return tryWriteFully(position, source); - } catch (InterruptedException e) { - throw new UncheckedIOException(new InterruptedIOException("read has been interrupted")); - } catch (ExecutionException e) { - throw new UncheckedIOException(new IOException(e)); - } - } - - private int tryWriteFully(long position, ByteBuffer source) throws InterruptedException, ExecutionException { - int count = source.remaining(); - long maxPosition = position + count; - do { - delegate.write(source, maxPosition - source.remaining()).get(); - } while (source.hasRemaining()); - return count; - } - - private void assertOpen() { - if (!openCloseCounter.isOpen()) { - throw new IllegalStateException("SharedFileChannel is not open"); - } - } - -} diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/WritableNioFile.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/WritableNioFile.java deleted file mode 100644 index 6db159bf6..000000000 --- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/WritableNioFile.java +++ /dev/null @@ -1,112 +0,0 @@ -package org.cryptomator.filesystem.nio; - -import static java.lang.String.format; -import static org.cryptomator.filesystem.nio.OpenMode.WRITE; - -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.nio.channels.ClosedChannelException; -import java.nio.file.Path; - -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.WritableFile; - -class WritableNioFile implements WritableFile { - - private final FileSystem fileSystem; - private final Path path; - private final SharedFileChannel channel; - private final Runnable afterCloseCallback; - - private boolean open = true; - private boolean channelOpened = false; - private long position = 0; - - public WritableNioFile(FileSystem fileSystem, Path path, SharedFileChannel channel, Runnable afterCloseCallback) { - this.fileSystem = fileSystem; - this.path = path; - this.channel = channel; - this.afterCloseCallback = afterCloseCallback; - } - - @Override - public int write(ByteBuffer source) throws UncheckedIOException { - assertOpen(); - ensureChannelIsOpened(); - int written = channel.writeFully(position, source); - position += written; - return written; - } - - @Override - public boolean isOpen() { - return open; - } - - @Override - public void position(long position) throws UncheckedIOException { - assertOpen(); - this.position = position; - } - - @Override - public void truncate() throws UncheckedIOException { - assertOpen(); - ensureChannelIsOpened(); - channel.truncate(0); - } - - @Override - public void close() throws UncheckedIOException { - if (!open) { - return; - } - open = false; - try { - closeChannelIfOpened(); - } finally { - afterCloseCallback.run(); - } - } - - void ensureChannelIsOpened() { - if (!channelOpened) { - channel.open(WRITE); - channelOpened = true; - } - } - - void closeChannelIfOpened() { - if (channelOpened) { - channel.close(); - } - } - - FileSystem fileSystem() { - return fileSystem; - } - - Path path() { - return path; - } - - SharedFileChannel channel() { - return channel; - } - - void invokeAfterCloseCallback() { - afterCloseCallback.run(); - } - - void assertOpen() { - if (!open) { - throw new UncheckedIOException(format("%s already closed.", this), new ClosedChannelException()); - } - } - - @Override - public String toString() { - return format("WritableNioFile(%s)", path); - } - -} \ No newline at end of file diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/DefaultInstanceFactoryTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/DefaultInstanceFactoryTest.java deleted file mode 100644 index 0aeb80c99..000000000 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/DefaultInstanceFactoryTest.java +++ /dev/null @@ -1,92 +0,0 @@ -package org.cryptomator.filesystem.nio; - -import static java.lang.String.format; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.Optional; - -import org.junit.Before; -import org.junit.Test; - -public class DefaultInstanceFactoryTest { - - private NioFileSystem fileSystem; - private Optional parent; - private Path path; - private NioAccess nioAccess; - private SharedFileChannel channel; - - private final DefaultInstanceFactory inTest = new DefaultInstanceFactory(); - - @Before - public void setUp() { - fileSystem = mock(NioFileSystem.class); - parent = Optional.of(fileSystem); - path = mock(Path.class); - channel = mock(SharedFileChannel.class); - when(path.toAbsolutePath()).thenReturn(path); - nioAccess = mock(NioAccess.class); - } - - @Test - public void testNioFolderCreatesNioFolder() { - NioFolder result = inTest.nioFolder(parent, path, nioAccess); - - assertThat(result.parent(), is(parent)); - assertThat(result.path(), is(path)); - - result.exists(); - verify(nioAccess).isDirectory(path); - } - - @Test - public void testNioFileCreatesNioFile() { - NioFile result = inTest.nioFile(parent, path, nioAccess); - - assertThat(result.parent(), is(parent)); - - result.exists(); - verify(nioAccess).isRegularFile(path); - } - - @Test - public void testSharedFileChannelCreatesSharedFileChannel() throws IOException { - SharedFileChannel result = inTest.sharedFileChannel(path, nioAccess); - - result.open(OpenMode.WRITE); - verify(nioAccess).open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE); - } - - @Test - public void testWritableNioFileCreatesWritableNioFile() throws IOException { - Runnable afterCloseCallback = mock(Runnable.class); - WritableNioFile result = inTest.writableNioFile(fileSystem, path, channel, afterCloseCallback); - - assertThat(result.path(), is(path)); - assertThat(result.channel(), is(channel)); - assertThat(result.fileSystem(), is(fileSystem)); - - result.close(); - verify(afterCloseCallback).run(); - } - - @Test - public void testReadableNioFileCreatesWritableNioFile() throws IOException { - Runnable afterCloseCallback = mock(Runnable.class); - ReadableNioFile result = inTest.readableNioFile(path, channel, afterCloseCallback); - - assertThat(result.toString(), is(format("ReadableNioFile(%s)", path))); - - result.close(); - verify(channel).close(); - verify(afterCloseCallback).run(); - } - -} diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/DefaultNioAccessTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/DefaultNioAccessTest.java deleted file mode 100644 index 3b767efbd..000000000 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/DefaultNioAccessTest.java +++ /dev/null @@ -1,217 +0,0 @@ -package org.cryptomator.filesystem.nio; - -import static java.nio.file.StandardCopyOption.ATOMIC_MOVE; -import static java.util.Arrays.asList; -import static java.util.stream.Collectors.toList; -import static org.cryptomator.common.test.matcher.ContainsMatcher.contains; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.same; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.nio.channels.AsynchronousFileChannel; -import java.nio.file.CopyOption; -import java.nio.file.DirectoryStream; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.LinkOption; -import java.nio.file.OpenOption; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.nio.file.attribute.BasicFileAttributeView; -import java.nio.file.attribute.BasicFileAttributes; -import java.nio.file.attribute.FileAttribute; -import java.nio.file.attribute.FileTime; -import java.nio.file.spi.FileSystemProvider; -import java.time.Instant; -import java.util.HashSet; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import de.bechte.junit.runners.context.HierarchicalContextRunner; - -@RunWith(HierarchicalContextRunner.class) -public class DefaultNioAccessTest { - - private DefaultNioAccess inTest = new DefaultNioAccess(); - - private Path path; - private Path otherPath; - - private FileSystem fileSystem; - private FileSystemProvider provider; - - @Before - public void setUp() { - path = mock(Path.class); - otherPath = mock(Path.class); - fileSystem = mock(FileSystem.class); - provider = mock(FileSystemProvider.class); - - when(fileSystem.provider()).thenReturn(provider); - when(path.getFileSystem()).thenReturn(fileSystem); - when(otherPath.getFileSystem()).thenReturn(fileSystem); - } - - @Test - public void testOpenCallsOpenOnProvider() throws IOException { - OpenOption[] options = {StandardOpenOption.APPEND}; - AsynchronousFileChannel channel = mock(AsynchronousFileChannel.class); - when(provider.newAsynchronousFileChannel(path, new HashSet<>(asList(options)), null, new FileAttribute[0])).thenReturn(channel); - - AsynchronousFileChannel result = inTest.open(path, options); - - assertThat(result, is(channel)); - } - - @Test - public void testIsRegularFileCallsIsRegularFileOnProvider() throws IOException { - LinkOption[] options = {LinkOption.NOFOLLOW_LINKS}; - BasicFileAttributes attributes = mock(BasicFileAttributes.class); - when(attributes.isRegularFile()).thenReturn(true); - when(provider.readAttributes(path, BasicFileAttributes.class, options)).thenReturn(attributes); - - boolean result = inTest.isRegularFile(path, options); - - assertThat(result, is(true)); - verify(attributes).isRegularFile(); - } - - @Test - public void testIsDirectoryCallsIsDirectoryOnProvider() throws IOException { - LinkOption[] options = {LinkOption.NOFOLLOW_LINKS}; - BasicFileAttributes attributes = mock(BasicFileAttributes.class); - when(attributes.isDirectory()).thenReturn(true); - when(provider.readAttributes(path, BasicFileAttributes.class, options)).thenReturn(attributes); - - boolean result = inTest.isDirectory(path, options); - - assertThat(result, is(true)); - verify(attributes).isDirectory(); - } - - @Test - public void testExistsCallsProviderCheckAccessAndReturnsTrueIfItWorks() throws IOException { - boolean result = inTest.exists(path); - - assertThat(result, is(true)); - verify(provider).checkAccess(path); - } - - @Test - public void testExistsCallsProviderCheckAccessAndReturnsFalseIfItThrowsAFileNotFoundException() throws IOException { - doThrow(new FileNotFoundException()).when(provider).checkAccess(path); - - boolean result = inTest.exists(path); - - assertThat(result, is(false)); - } - - @Test - public void testListReturnsStreamCreatedFromDirectoryStream() throws IOException { - @SuppressWarnings("unchecked") - DirectoryStream directoryStream = mock(DirectoryStream.class); - Path aPath = mock(Path.class); - Path anotherPath = mock(Path.class); - when(directoryStream.iterator()).thenReturn(asList(aPath, anotherPath).iterator()); - when(provider.newDirectoryStream(same(path), any())).thenReturn(directoryStream); - - assertThat(inTest.list(path).collect(toList()), contains(is(aPath), is(anotherPath))); - } - - @Test - public void testCreateDirectoriesCreatesDirectoryUsingProvider() throws IOException { - FileAttribute[] attributes = {mock(FileAttribute.class)}; - - inTest.createDirectories(path, attributes); - - verify(provider).createDirectory(path, attributes); - } - - @Test - public void testGetLastModifiedTimeGetsLastModifiedTimeUsingProvider() throws IOException { - LinkOption[] options = {LinkOption.NOFOLLOW_LINKS}; - FileTime expectedResult = FileTime.from(Instant.now()); - BasicFileAttributes attributes = mock(BasicFileAttributes.class); - when(attributes.lastModifiedTime()).thenReturn(expectedResult); - when(provider.readAttributes(path, BasicFileAttributes.class, options)).thenReturn(attributes); - - FileTime result = inTest.getLastModifiedTime(path, options); - - assertThat(result, is(expectedResult)); - } - - @Test - public void testSetLastModifiedTimeSetsLastModifiedTimeUsingProvider() throws IOException { - FileTime fileTime = FileTime.from(Instant.now()); - BasicFileAttributeView attributes = mock(BasicFileAttributeView.class); - when(provider.getFileAttributeView(path, BasicFileAttributeView.class)).thenReturn(attributes); - - inTest.setLastModifiedTime(path, fileTime); - - verify(attributes).setTimes(fileTime, null, null); - } - - @Test - public void testDeleteDeletesUsingProvider() throws IOException { - inTest.delete(path); - - verify(provider).delete(path); - } - - @Test - public void testDeleteMovesUsingProvider() throws IOException { - CopyOption[] options = {ATOMIC_MOVE}; - inTest.move(path, otherPath, options); - - verify(provider).move(path, otherPath, options); - } - - @Test - public void testCloseInvokesCloseOnChannel() throws IOException { - AsynchronousFileChannel channel = mock(AsynchronousFileChannel.class); - - inTest.close(channel); - - verify(channel).close(); - } - - @Test - public void testGetCreationTimeReadsAttributesUsingProviderAndReturnsValueFromThem() throws IOException { - FileTime expectedValue = FileTime.from(Instant.parse("2016-01-08T22:32:00Z")); - BasicFileAttributes attributes = mock(BasicFileAttributes.class); - when(attributes.creationTime()).thenReturn(expectedValue); - LinkOption[] options = {LinkOption.NOFOLLOW_LINKS}; - when(provider.readAttributes(path, BasicFileAttributes.class, options)).thenReturn(attributes); - - FileTime result = inTest.getCreationTime(path, options); - - assertThat(result, is(expectedValue)); - } - - @Test - public void testSetCreationTimeGetsAttributeViewUsingProviderAndSetsCreationTimeUsingIt() throws IOException { - FileTime fileTime = FileTime.from(Instant.now()); - BasicFileAttributeView attributes = mock(BasicFileAttributeView.class); - LinkOption[] options = {LinkOption.NOFOLLOW_LINKS}; - when(provider.getFileAttributeView(path, BasicFileAttributeView.class, options)).thenReturn(attributes); - - inTest.setCreationTime(path, fileTime, options); - - verify(attributes).setTimes(null, null, fileTime); - } - - @Test - public void testSeparatorReturnsSeparatorOfDefaultFileSystem() { - assertThat(inTest.separator(), is(FileSystems.getDefault().getSeparator())); - } - -} diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/InstanceFactoryTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/InstanceFactoryTest.java deleted file mode 100644 index 742148116..000000000 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/InstanceFactoryTest.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.cryptomator.filesystem.nio; - -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; - -import org.junit.Test; - -public class InstanceFactoryTest { - - @Test - public void testInitialDefaultIsDefaultInstanceFactory() { - InstanceFactory.DEFAULT.reset(); - - assertThat(InstanceFactory.DEFAULT.get(), is(instanceOf(DefaultInstanceFactory.class))); - } - -} diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioAccessTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioAccessTest.java deleted file mode 100644 index e780c9ef1..000000000 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioAccessTest.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.cryptomator.filesystem.nio; - -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; - -import org.junit.Test; - -public class NioAccessTest { - - @Test - public void testInitialDefaultIsDefaultNioAccess() { - NioAccess.DEFAULT.reset(); - - assertThat(NioAccess.DEFAULT.get(), is(instanceOf(DefaultNioAccess.class))); - } - -} diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileSystemTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileSystemTest.java deleted file mode 100644 index 07ef1eb66..000000000 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileSystemTest.java +++ /dev/null @@ -1,60 +0,0 @@ -package org.cryptomator.filesystem.nio; - -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -public class NioFileSystemTest { - - private Path path; - - private InstanceFactory instanceFactory; - - private NioAccess nioAccess; - - private NioFileSystem inTest; - - @Before - public void setUp() { - path = mock(Path.class); - instanceFactory = mock(InstanceFactory.class); - nioAccess = mock(NioAccess.class); - - when(path.toAbsolutePath()).thenReturn(path); - - InstanceFactory.DEFAULT.set(instanceFactory); - NioAccess.DEFAULT.set(nioAccess); - - inTest = NioFileSystem.rootedAt(path); - } - - @Test - public void testRootedAtCreatesNioFileSystemWithPath() { - assertThat(inTest.instanceFactory, is(instanceFactory)); - assertThat(inTest.path, is(path)); - assertThat(inTest.nioAccess, is(nioAccess)); - assertThat(inTest.parent, is(Optional.empty())); - } - - @Test - public void testRootedAtCreatesFolder() throws IOException { - verify(nioAccess).createDirectories(path); - } - - @After - public void tearDown() { - InstanceFactory.DEFAULT.reset(); - NioAccess.DEFAULT.reset(); - } - -} \ No newline at end of file diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileTest.java deleted file mode 100644 index 05dbe75e7..000000000 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileTest.java +++ /dev/null @@ -1,517 +0,0 @@ -package org.cryptomator.filesystem.nio; - -import static java.lang.String.format; -import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; -import static org.cryptomator.common.test.matcher.OptionalMatcher.emptyOptional; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.same; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.NoSuchFileException; -import java.nio.file.Path; -import java.nio.file.attribute.FileTime; -import java.time.Instant; -import java.util.Optional; - -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.filesystem.WritableFile; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.InOrder; - -import de.bechte.junit.runners.context.HierarchicalContextRunner; - -@RunWith(HierarchicalContextRunner.class) -public class NioFileTest { - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private NioFileSystem fileSystem; - - private Optional parent; - - private Path path; - private NioAccess nioAccess; - private InstanceFactory instanceFactory; - - private SharedFileChannel channel; - - private NioFile inTest; - - @Before - public void setUp() { - fileSystem = mock(NioFileSystem.class); - path = mock(Path.class); - nioAccess = mock(NioAccess.class); - instanceFactory = mock(InstanceFactory.class); - channel = mock(SharedFileChannel.class); - parent = Optional.of(fileSystem); - - Path maybeNonAbsolutePath = mock(Path.class); - when(maybeNonAbsolutePath.toAbsolutePath()).thenReturn(path); - - when(fileSystem.fileSystem()).thenReturn(fileSystem); - when(instanceFactory.sharedFileChannel(path, nioAccess)).thenReturn(channel); - - inTest = new NioFile(parent, maybeNonAbsolutePath, nioAccess, instanceFactory); - } - - public class Constructor { - - @Test - public void testConstructorCreatesASharedFileChannelFromAbsolutePathAndNioAccessUsingTheInstanceFactory() { - verify(instanceFactory).sharedFileChannel(path, nioAccess); - } - - @Test - public void testConstructorSetsParentPassedToIt() { - assertThat(inTest.parent(), is(parent)); - } - - } - - public class Size { - - @Test - public void testSizeReturnsSizeOfRegularFile() throws IOException { - when(nioAccess.size(path)).thenReturn(42l); - - assertThat(inTest.size(), is(42l)); - } - - @Test - public void testSizeThrowsExceptionIfRegularFileThrowsException() throws IOException { - Throwable t = new NoSuchFileException("foo"); - when(nioAccess.size(path)).thenThrow(t); - - thrown.expect(UncheckedIOException.class); - thrown.expectCause(org.hamcrest.Matchers.sameInstance(t)); - inTest.size(); - } - - } - - public class Open { - - @Test - public void testOpenReadableCreatesReadableNioFileFromNioFile() { - ReadableNioFile readableNioFile = mock(ReadableNioFile.class); - when(instanceFactory.readableNioFile(same(path), same(channel), any())).thenReturn(readableNioFile); - - ReadableFile readableFile = inTest.openReadable(); - - assertThat(readableFile, is(readableNioFile)); - } - - @Test - public void testOpenReadableInvokedBeforeInvokingAfterCloseOperationThrowsIllegalStateException() { - when(instanceFactory.readableNioFile(same(path), same(channel), any())).thenReturn(mock(ReadableNioFile.class)); - inTest.openReadable(); - - thrown.expect(IllegalStateException.class); - thrown.expectMessage("already reading " + path); - - inTest.openReadable(); - } - - @Test - public void testOpenReadableInvokedAfterAfterCloseOperationCreatesNewReadableFile() { - ReadableNioFile readableNioFile = mock(ReadableNioFile.class); - ArgumentCaptor captor = ArgumentCaptor.forClass(Runnable.class); - when(instanceFactory.readableNioFile(same(path), same(channel), captor.capture())).thenReturn(mock(ReadableNioFile.class), readableNioFile); - inTest.openReadable(); - captor.getValue().run(); - - ReadableFile readableFile = inTest.openReadable(); - - assertThat(readableFile, is(readableNioFile)); - } - - @Test - public void testOpenReadableInvokedBeforeInvokingAfterCloseOperationOfOpenWritableThrowsIllegalStateException() { - when(instanceFactory.writableNioFile(same(fileSystem), same(path), same(channel), any())).thenReturn(mock(WritableNioFile.class)); - inTest.openWritable(); - - thrown.expect(IllegalStateException.class); - thrown.expectMessage("currently writing " + path); - - inTest.openReadable(); - } - - @Test - public void testOpenReadableInvokedAfterInvokingAfterCloseOperationWorks() { - ArgumentCaptor captor = ArgumentCaptor.forClass(Runnable.class); - when(instanceFactory.writableNioFile(same(fileSystem), same(path), same(channel), captor.capture())).thenReturn(mock(WritableNioFile.class)); - inTest.openWritable(); - captor.getValue().run(); - - inTest.openReadable(); - } - - @Test - public void testOpenWritableCreatesWritableNioFileFromNioFileAndNioAccessUsingInstanceFactory() { - WritableNioFile writableNioFile = mock(WritableNioFile.class); - when(instanceFactory.writableNioFile(same(fileSystem), same(path), same(channel), any())).thenReturn(writableNioFile); - - WritableFile writableFile = inTest.openWritable(); - - assertThat(writableFile, is(writableNioFile)); - } - - @Test - public void testOpenWritableInvokedAfterAfterCloseOperationCreatesNewWritableFile() { - WritableNioFile writableNioFile = mock(WritableNioFile.class); - ArgumentCaptor captor = ArgumentCaptor.forClass(Runnable.class); - when(instanceFactory.writableNioFile(same(fileSystem), same(path), same(channel), captor.capture())).thenReturn(mock(WritableNioFile.class), writableNioFile); - inTest.openWritable(); - captor.getValue().run(); - - WritableFile writableFile = inTest.openWritable(); - - assertThat(writableFile, is(writableNioFile)); - } - - @Test - public void testOpenWritableInvokedBeforeInvokingAfterCloseOperationThrowsIllegalStateException() { - when(instanceFactory.writableNioFile(same(fileSystem), same(path), same(channel), any())).thenReturn(mock(WritableNioFile.class)); - inTest.openWritable(); - - thrown.expect(IllegalStateException.class); - thrown.expectMessage("already writing " + path); - - inTest.openWritable(); - } - - @Test - public void testOpenWritableInvokedBeforeInvokingAfterCloseOperationFromOpenReadableThrowsIllegalStateException() { - when(instanceFactory.readableNioFile(same(path), same(channel), any())).thenReturn(mock(ReadableNioFile.class)); - inTest.openReadable(); - - thrown.expect(IllegalStateException.class); - thrown.expectMessage("currently reading " + path); - - inTest.openWritable(); - } - - @Test - public void testOpenWritableInvokedAfterInvokingAfterCloseOperationWorks() { - ReadableNioFile readableNioFile = mock(ReadableNioFile.class); - ArgumentCaptor captor = ArgumentCaptor.forClass(Runnable.class); - when(instanceFactory.readableNioFile(same(path), same(channel), captor.capture())).thenReturn(readableNioFile); - inTest.openReadable(); - captor.getValue().run(); - - inTest.openWritable(); - } - - } - - public class MoveTo { - - private NioFile target; - - private Path pathOfTarget; - - @Before - public void setUp() { - target = mock(NioFile.class); - pathOfTarget = mock(Path.class); - when(target.fileSystem()).thenReturn(fileSystem); - when(target.path()).thenReturn(pathOfTarget); - } - - @Test - public void testMoveToFailsIfTargetIsNoWritableNioFile() { - thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("Can only move to a File from the same FileSystem"); - - inTest.moveTo(mock(File.class)); - } - - @Test - public void testMoveToFailsIfTargetIsNotFromSameFileSystem() { - NioFile targetFromOtherFileSystem = mock(NioFile.class); - when(targetFromOtherFileSystem.fileSystem()).thenReturn(mock(FileSystem.class)); - - thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("Can only move to a File from the same FileSystem"); - - inTest.moveTo(targetFromOtherFileSystem); - } - - @Test - public void testMoveToFailsIfTargetIsDirectory() { - when(nioAccess.isDirectory(path)).thenReturn(false); - when(nioAccess.isDirectory(pathOfTarget)).thenReturn(true); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage("Target is a directory"); - thrown.expectMessage(path.toString()); - thrown.expectMessage(pathOfTarget.toString()); - - inTest.moveTo(target); - } - - @Test - public void testMoveToDoesNothingIfTargetIsSameInstance() { - inTest.moveTo(inTest); - - verifyZeroInteractions(nioAccess); - } - - @Test - public void testMoveToWrapsIOExceptionFromMoveInUncheckedIOException() throws IOException { - when(nioAccess.isDirectory(path)).thenReturn(false); - when(nioAccess.isDirectory(pathOfTarget)).thenReturn(false); - IOException exceptionFromMove = new IOException(); - doThrow(exceptionFromMove).when(nioAccess).move(path, pathOfTarget, REPLACE_EXISTING); - - thrown.expect(UncheckedIOException.class); - thrown.expectCause(is(exceptionFromMove)); - - inTest.moveTo(target); - } - - } - - public class Exists { - - @Test - public void testExistsReturnsTrueIfPathIsRegularFile() { - when(nioAccess.isRegularFile(path)).thenReturn(true); - - assertThat(inTest.exists(), is(true)); - } - - @Test - public void testExistsReturnsFalseIfPathIsntRegularFile() { - when(nioAccess.isRegularFile(path)).thenReturn(false); - - assertThat(inTest.exists(), is(false)); - } - - } - - public class LastModified { - - @Test - public void testLastModifiedReturnsLastModifiedTimeForExistingFile() throws IOException { - Instant instant = Instant.parse("2016-01-04T01:24:32Z"); - when(nioAccess.exists(path)).thenReturn(true); - when(nioAccess.isRegularFile(path)).thenReturn(true); - when(nioAccess.getLastModifiedTime(path)).thenReturn(FileTime.from(instant)); - - Instant result = inTest.lastModified(); - - assertThat(result, is(instant)); - } - - @Test - public void testLastModifiedInvokesGetLastModifiedTimeForNonExistingFile() throws IOException { - Instant instant = Instant.parse("2016-01-04T01:24:32Z"); - when(nioAccess.exists(path)).thenReturn(false); - when(nioAccess.getLastModifiedTime(path)).thenReturn(FileTime.from(instant)); - - Instant result = inTest.lastModified(); - - assertThat(result, is(instant)); - } - - @Test - public void testLastModifiedWrapsIOExceptionThrownByGetLastModifiedTimeInUncheckedIOException() throws IOException { - IOException exceptionThrownFromGetLastModifiedTime = new IOException(); - when(nioAccess.exists(path)).thenReturn(true); - when(nioAccess.isRegularFile(path)).thenReturn(true); - when(nioAccess.getLastModifiedTime(path)).thenThrow(exceptionThrownFromGetLastModifiedTime); - - thrown.expect(UncheckedIOException.class); - thrown.expectCause(is(exceptionThrownFromGetLastModifiedTime)); - - inTest.lastModified(); - } - - @Test - public void testLastModifiedThrowsUncheckedIOExceptionIfPathExistsButIsNoRegularFile() { - when(nioAccess.exists(path)).thenReturn(true); - when(nioAccess.isRegularFile(path)).thenReturn(false); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(format("%s is a folder", path)); - - inTest.lastModified(); - } - - } - - public class SetLastModified { - - @Test - public void testSetLastModifiedOpensChannelIfClosedAndSetsLastModifiedTime() throws IOException { - Instant instant = Instant.parse("2016-01-05T13:51:00Z"); - FileTime time = FileTime.from(instant); - - inTest.setLastModified(instant); - - InOrder inOrder = inOrder(channel, nioAccess); - inOrder.verify(nioAccess).setLastModifiedTime(path, time); - } - - @Test - public void testSetLastModifiedWrapsIOExceptionFromSetLastModifiedInUncheckedIOException() throws IOException { - IOException exceptionFromSetLastModified = new IOException(); - Instant instant = Instant.parse("2016-01-05T13:51:00Z"); - FileTime time = FileTime.from(instant); - doThrow(exceptionFromSetLastModified).when(nioAccess).setLastModifiedTime(path, time); - - thrown.expect(UncheckedIOException.class); - thrown.expectCause(is(exceptionFromSetLastModified)); - - inTest.setLastModified(instant); - } - - } - - public class CreationTime { - - @Test - public void testCreationTimeDelegatesToNioAccessCreationTime() throws IOException { - Instant exectedResult = Instant.parse("1970-01-02T00:00:00Z"); - when(nioAccess.getCreationTime(path)).thenReturn(FileTime.from(exectedResult)); - when(nioAccess.exists(path)).thenReturn(true); - when(nioAccess.isRegularFile(path)).thenReturn(true); - - Instant result = inTest.creationTime().get(); - - assertThat(result, is(exectedResult)); - } - - @Test - public void testCreationTimeReturnsEmptyOptionalIfNioAccessCreationTimeReturnsValueBeforeJanuaryTheSecondNineteenhundredSeventy() throws IOException { - when(nioAccess.getCreationTime(path)).thenReturn(FileTime.from(Instant.parse("1970-01-01T23:59:59Z"))); - when(nioAccess.exists(path)).thenReturn(true); - when(nioAccess.isRegularFile(path)).thenReturn(true); - - assertThat(inTest.creationTime(), is(emptyOptional())); - } - - @Test - public void testCreationTimeWrapsIOExceptionFromNioAccessCreationTimeInUncheckedIOException() throws IOException { - IOException exceptionFromCreationTime = new IOException(); - when(nioAccess.getCreationTime(path)).thenThrow(exceptionFromCreationTime); - when(nioAccess.exists(path)).thenReturn(true); - when(nioAccess.isRegularFile(path)).thenReturn(true); - - thrown.expect(UncheckedIOException.class); - thrown.expectCause(is(exceptionFromCreationTime)); - - inTest.creationTime(); - } - - @Test - public void testCreationTimeThrowsExceptionIfFileIsNoRegularFile() { - when(nioAccess.exists(path)).thenReturn(true); - when(nioAccess.isRegularFile(path)).thenReturn(false); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(format("%s is a folder", path)); - - inTest.creationTime(); - } - - } - - public class SetCreationTime { - - @Test - public void testSetCreationTimeOpensChannelIfClosedAndInvokesNioAccessSetCreationTimeAfterwards() throws IOException { - Instant instant = Instant.parse("2016-01-08T22:32:00Z"); - - inTest.setCreationTime(instant); - - InOrder inOrder = inOrder(nioAccess, channel); - inOrder.verify(nioAccess).setCreationTime(path, FileTime.from(instant)); - } - - @Test - public void testSetCreationTimeWrapsIOExceptionFromSetCreationTimeInUncheckedIOException() throws IOException { - IOException exceptionFromSetCreationTime = new IOException(); - Instant irrelevant = Instant.now(); - doThrow(exceptionFromSetCreationTime).when(nioAccess).setCreationTime(same(path), any()); - - thrown.expect(UncheckedIOException.class); - thrown.expectCause(is(exceptionFromSetCreationTime)); - - inTest.setCreationTime(irrelevant); - } - - } - - public class CompareTo { - - private Path otherPath; - private NioFile otherInTest; - - @Before - public void setUp() { - otherPath = mock(Path.class); - - Path maybeNonAbsolutePath = mock(Path.class); - when(maybeNonAbsolutePath.toAbsolutePath()).thenReturn(otherPath); - - otherInTest = new NioFile(parent, maybeNonAbsolutePath, nioAccess, instanceFactory); - } - - @Test - public void testCompareToFileFromOtherFileSystemThrowsIllegalArgumentException() { - File other = mock(File.class); - when(other.fileSystem()).thenReturn(mock(FileSystem.class)); - - thrown.expect(IllegalArgumentException.class); - - inTest.compareTo(other); - } - - @Test - public void testCompareToReturnsResultOfPathsCompareTo() { - int expectedResult = 2873; - when(path.compareTo(otherPath)).thenReturn(expectedResult); - - int result = inTest.compareTo(otherInTest); - - assertThat(result, is(expectedResult)); - } - - } - - @Test - public void testNameReturnsFileNameOfPath() { - Path fileName = mock(Path.class); - when(path.getFileName()).thenReturn(fileName); - - String name = inTest.name(); - - assertThat(name, is(fileName.toString())); - } - - @Test - public void testToString() { - assertThat(inTest.toString(), is(format("NioFile(%s)", path))); - } - -} \ No newline at end of file diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFolderTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFolderTest.java deleted file mode 100644 index ddc47e3fc..000000000 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFolderTest.java +++ /dev/null @@ -1,557 +0,0 @@ -package org.cryptomator.filesystem.nio; - -import static java.lang.String.format; -import static java.util.stream.Collectors.toList; -import static org.cryptomator.common.test.matcher.ContainsMatcher.contains; -import static org.cryptomator.filesystem.nio.ReflectiveClassMatchers.aClassThatDoesDeclareMethod; -import static org.cryptomator.filesystem.nio.ReflectiveClassMatchers.aClassThatDoesNotDeclareMethod; -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.theInstance; -import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Path; -import java.nio.file.attribute.FileTime; -import java.time.Instant; -import java.util.Optional; -import java.util.function.Supplier; -import java.util.stream.Stream; - -import org.cryptomator.common.streams.AutoClosingStream; -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.WritableFile; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; -import org.mockito.InOrder; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -import de.bechte.junit.runners.context.HierarchicalContextRunner; - -@RunWith(HierarchicalContextRunner.class) -public class NioFolderTest { - - private static final String SEPARATOR = "/"; - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private NioFileSystem fileSystem; - private Optional parent; - - private Path path; - private NioAccess nioAccess; - private InstanceFactory instanceFactory; - - private NioFolder inTest; - - @Before - public void setUp() { - path = mock(Path.class); - nioAccess = mock(NioAccess.class); - instanceFactory = mock(InstanceFactory.class); - fileSystem = mock(NioFileSystem.class); - parent = Optional.of(fileSystem); - - Path maybeNonAbsolutePath = mock(Path.class); - when(maybeNonAbsolutePath.toAbsolutePath()).thenReturn(path); - - when(parent.get().fileSystem()).thenReturn(fileSystem); - - when(nioAccess.separator()).thenReturn(SEPARATOR); - - inTest = new NioFolder(parent, maybeNonAbsolutePath, nioAccess, instanceFactory); - } - - public class ChildrenTests { - - @Test - public void testChildrenReturnsAnAutoClosingStream() throws IOException { - Stream childrenPaths = Stream.builder().build(); - when(nioAccess.list(path)).thenReturn(childrenPaths); - - assertThat(inTest.children(), is(instanceOf(AutoClosingStream.class))); - } - - @Test - public void testChildrenConvertsPathWhichIsADirectoryToAnNioFolderUsingTheInstanceFactory() throws IOException { - Path childFolderPath = mock(Path.class); - NioFolder nioFolderCreatedFromChildFolderPath = mock(NioFolder.class); - Stream childrenPaths = Stream.builder().add(childFolderPath).build(); - when(nioAccess.isDirectory(childFolderPath)).thenReturn(true); - when(nioAccess.list(path)).thenReturn(childrenPaths); - when(instanceFactory.nioFolder(Optional.of(inTest), childFolderPath, nioAccess)).thenReturn(nioFolderCreatedFromChildFolderPath); - - assertThat(inTest.children().collect(toList()), contains(theInstance(nioFolderCreatedFromChildFolderPath))); - } - - @Test - public void testChildrenConvertsPathWhichIsAFileToAnNioFileUsingTheInstanceFactory() throws IOException { - Path childFilePath = mock(Path.class); - NioFile nioFileCreatedFromChildFolderPath = mock(NioFile.class); - Stream childrenPaths = Stream.builder().add(childFilePath).build(); - when(nioAccess.isDirectory(childFilePath)).thenReturn(false); - when(nioAccess.list(path)).thenReturn(childrenPaths); - when(instanceFactory.nioFile(Optional.of(inTest), childFilePath, nioAccess)).thenReturn(nioFileCreatedFromChildFolderPath); - - assertThat(inTest.children().collect(toList()), contains(theInstance(nioFileCreatedFromChildFolderPath))); - } - - @Test - public void testChildrenConvertsAllPathsToFilesAndFolders() throws IOException { - Path childFilePath = mock(Path.class); - Path childFolderPath = mock(Path.class); - Path anotherChildFilePath = mock(Path.class); - Path anotherChildFolderPath = mock(Path.class); - NioFile nioFileCreatedFromChildFilePath = mock(NioFile.class); - NioFile anotherNioFileCreatedFromChildFilePath = mock(NioFile.class); - NioFolder nioFolderCreatedFromChildFolderPath = mock(NioFolder.class); - NioFolder anotherNioFolderCreatedFromChildFolderPath = mock(NioFolder.class); - Stream childrenPaths = Stream.builder() // - .add(childFilePath) // NioFolder - .add(childFolderPath) // - .add(anotherChildFilePath) // - .add(anotherChildFolderPath).build(); - when(nioAccess.isDirectory(childFilePath)).thenReturn(false); - when(nioAccess.isDirectory(childFolderPath)).thenReturn(true); - when(nioAccess.isDirectory(anotherChildFilePath)).thenReturn(false); - when(nioAccess.isDirectory(anotherChildFolderPath)).thenReturn(true); - when(nioAccess.list(path)).thenReturn(childrenPaths); - when(instanceFactory.nioFile(Optional.of(inTest), childFilePath, nioAccess)).thenReturn(nioFileCreatedFromChildFilePath); - when(instanceFactory.nioFolder(Optional.of(inTest), childFolderPath, nioAccess)).thenReturn(nioFolderCreatedFromChildFolderPath); - when(instanceFactory.nioFile(Optional.of(inTest), anotherChildFilePath, nioAccess)).thenReturn(anotherNioFileCreatedFromChildFilePath); - when(instanceFactory.nioFolder(Optional.of(inTest), anotherChildFolderPath, nioAccess)).thenReturn(anotherNioFolderCreatedFromChildFolderPath); - - assertThat(inTest.children().collect(toList()), - contains( // - theInstance(nioFileCreatedFromChildFilePath), // - theInstance(nioFolderCreatedFromChildFolderPath), // - theInstance(anotherNioFileCreatedFromChildFilePath), // - theInstance(anotherNioFolderCreatedFromChildFolderPath))); - } - - @Test - public void testFilesIsNotOverwritten() { - assertThat(Folder.class, aClassThatDoesDeclareMethod("files")); - assertThat(NioFolder.class, aClassThatDoesNotDeclareMethod("files")); - } - - @Test - public void testFoldersIsNotOverwritten() { - assertThat(Folder.class, aClassThatDoesDeclareMethod("folders")); - assertThat(NioFolder.class, aClassThatDoesNotDeclareMethod("folders")); - } - - @Test - public void testChildrenWrapsIOExceptionFromListInUncheckedIOException() throws IOException { - IOException exceptionFromList = new IOException(); - when(nioAccess.list(path)).thenThrow(exceptionFromList); - - thrown.expect(UncheckedIOException.class); - thrown.expectCause(is(exceptionFromList)); - - inTest.children(); - } - - } - - public class FileTests { - - @Test - public void testFileResolvesTheNameAgainstThePathAndCreatesAnNioFileUsingTheInstanceFactory() { - String name = "theFileName"; - Path resolvedPath = mock(Path.class); - NioFile fileCreatedByInstanceFactory = mock(NioFile.class); - when(path.resolve(name)).thenReturn(resolvedPath); - when(instanceFactory.nioFile(Optional.of(inTest), resolvedPath, nioAccess)).thenReturn(fileCreatedByInstanceFactory); - - File result = inTest.file(name); - - assertThat(result, is(fileCreatedByInstanceFactory)); - } - - @Test - public void testSecondInvocationOfFileReturnsChachedResult() { - String name = "theFileName"; - Path resolvedPath = mock(Path.class); - NioFile fileCreatedByInstanceFactory = mock(NioFile.class); - when(path.resolve(name)).thenReturn(resolvedPath); - when(instanceFactory.nioFile(Optional.of(inTest), resolvedPath, nioAccess)).thenReturn(fileCreatedByInstanceFactory); - - File result = inTest.file(name); - File secondResult = inTest.file(name); - - assertThat(result, is(fileCreatedByInstanceFactory)); - assertThat(secondResult, is(fileCreatedByInstanceFactory)); - verify(instanceFactory).nioFile(Optional.of(inTest), resolvedPath, nioAccess); - } - - @Test - public void testFileInvokedWithNameContainingSeparatorThrowsIllegalArgumentException() { - String name = "nameContaining" + SEPARATOR; - - thrown.expect(IllegalArgumentException.class); - thrown.expectMessage(name); - - inTest.file(name); - } - - @Test - public void testResolveFileIsNotOverwritten() { - assertThat(Folder.class, is(aClassThatDoesDeclareMethod("resolveFile", String.class))); - assertThat(NioFolder.class, is(aClassThatDoesNotDeclareMethod("resolveFile", String.class))); - } - - } - - public class FolderTests { - - @Test - public void testFolderResolvesTheNameAgainstThePathAndCreatesAnNioFolderUsingTheInstanceFactory() { - String name = "theFolderName"; - Path resolvedPath = mock(Path.class); - NioFolder folderCreatedByInstanceFactory = mock(NioFolder.class); - when(path.resolve(name)).thenReturn(resolvedPath); - when(instanceFactory.nioFolder(Optional.of(inTest), resolvedPath, nioAccess)).thenReturn(folderCreatedByInstanceFactory); - - Folder result = inTest.folder(name); - - assertThat(result, is(folderCreatedByInstanceFactory)); - } - - @Test - public void testSecondInvocationOfFileReturnsChachedResult() { - String name = "theFolderName"; - Path resolvedPath = mock(Path.class); - NioFolder folderCreatedByInstanceFactory = mock(NioFolder.class); - when(path.resolve(name)).thenReturn(resolvedPath); - when(instanceFactory.nioFolder(Optional.of(inTest), resolvedPath, nioAccess)).thenReturn(folderCreatedByInstanceFactory); - - Folder result = inTest.folder(name); - Folder secondResult = inTest.folder(name); - - assertThat(result, is(folderCreatedByInstanceFactory)); - assertThat(secondResult, is(folderCreatedByInstanceFactory)); - verify(instanceFactory).nioFolder(Optional.of(inTest), resolvedPath, nioAccess); - } - - @Test - public void testFolderInvokedWithNameContainingSeparatorThrowsIllegalArgumentException() { - String name = "nameContaining" + SEPARATOR; - - thrown.expect(IllegalArgumentException.class); - thrown.expectMessage(name); - - inTest.folder(name); - } - - @Test - public void testResolveFolderIsNotOverwritten() { - assertThat(Folder.class, is(aClassThatDoesDeclareMethod("resolveFolder", String.class))); - assertThat(NioFolder.class, is(aClassThatDoesNotDeclareMethod("resolveFolder", String.class))); - } - - } - - public class CreateTests { - - @Test - public void testCreateDelegatesToNioAccessCreateDirectories() throws IOException { - inTest.create(); - - verify(nioAccess).createDirectories(path); - } - - @Test - public void testWrapesIOExceptionThrownByCreateDirectoriesInUncheckedIOException() throws IOException { - IOException exceptionFromCreateDirectories = new IOException(); - doThrow(exceptionFromCreateDirectories).when(nioAccess).createDirectories(path); - - thrown.expect(UncheckedIOException.class); - thrown.expectCause(is(exceptionFromCreateDirectories)); - - inTest.create(); - } - - } - - public class LastModifiedTests { - - @Test - public void testLastModifiedReturnsLastModifiedTimeForExistingFolder() throws IOException { - Instant instant = Instant.parse("2016-01-04T01:24:32Z"); - when(nioAccess.exists(path)).thenReturn(true); - when(nioAccess.isDirectory(path)).thenReturn(true); - when(nioAccess.getLastModifiedTime(path)).thenReturn(FileTime.from(instant)); - - Instant result = inTest.lastModified(); - - assertThat(result, is(instant)); - } - - @Test - public void testLastModifiedInvokesGetLastModifiedTimeForNonExistingFolder() throws IOException { - Instant instant = Instant.parse("2016-01-04T01:24:32Z"); - when(nioAccess.exists(path)).thenReturn(false); - when(nioAccess.getLastModifiedTime(path)).thenReturn(FileTime.from(instant)); - - Instant result = inTest.lastModified(); - - assertThat(result, is(instant)); - } - - @Test - public void testLastModifiedWrapsIOExceptionThrownByGetLastModifiedTimeInUncheckedIOException() throws IOException { - IOException exceptionThrownFromGetLastModifiedTime = new IOException(); - when(nioAccess.exists(path)).thenReturn(true); - when(nioAccess.isDirectory(path)).thenReturn(true); - when(nioAccess.getLastModifiedTime(path)).thenThrow(exceptionThrownFromGetLastModifiedTime); - - thrown.expect(UncheckedIOException.class); - thrown.expectCause(is(exceptionThrownFromGetLastModifiedTime)); - - inTest.lastModified(); - } - - @Test - public void testLastModifiedThrowsUncheckedIOExceptionIfPathExistsButIsNoFolder() { - when(nioAccess.exists(path)).thenReturn(true); - when(nioAccess.isDirectory(path)).thenReturn(false); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(format("%s is a file", path)); - - inTest.lastModified(); - } - - } - - public class ExistTests { - - @Test - public void testExistsIfExisting() { - when(nioAccess.isDirectory(path)).thenReturn(true); - - assertThat(inTest.exists(), is(true)); - } - - @Test - public void testExistsIfNotExisting() { - when(nioAccess.isDirectory(path)).thenReturn(false); - - assertThat(inTest.exists(), is(false)); - } - - } - - public class MoveToTests { - - @Test - public void testMoveToThrowsIllegalArgumentExceptionIfTargetDoesNotBelongToSameFilesystem() { - Folder target = mock(Folder.class); - when(target.fileSystem()).thenReturn(mock(FileSystem.class)); - - thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("Can only move a Folder to a Folder in the same FileSystem"); - - inTest.moveTo(target); - } - - @Test - public void testMoveToWrapsIOExceptionThrownByNioAccessMoveInUncheckedIOException() throws IOException { - NioFolder target = mock(NioFolder.class); - Path targetPath = mock(Path.class); - when(target.fileSystem()).thenReturn(fileSystem); - when(target.path()).thenReturn(targetPath); - when(target.parent()).thenReturn(Optional.empty()); - IOException exceptionThrownByMoveTo = new IOException(); - doThrow(exceptionThrownByMoveTo).when(nioAccess).move(path, targetPath); - - thrown.expect(UncheckedIOException.class); - thrown.expectCause(is(exceptionThrownByMoveTo)); - - inTest.moveTo(target); - } - - @Test - public void testMoveToDeletesTargetAndDelegatesToNioAccessMoveIfTargetHasNoParent() throws IOException { - NioFolder target = mock(NioFolder.class); - Path targetPath = mock(Path.class); - when(target.fileSystem()).thenReturn(fileSystem); - when(target.path()).thenReturn(targetPath); - when(target.parent()).thenReturn(Optional.empty()); - - inTest.moveTo(target); - - InOrder inOrder = inOrder(target, nioAccess); - inOrder.verify(target).delete(); - inOrder.verify(nioAccess).move(path, targetPath); - } - - @SuppressWarnings({"unchecked", "rawtypes"}) - @Test - public void testMoveToDeletesTargetCreatesTargetsParentAndDelegatesToNioAccessMoveIfTargetHasAParent() throws IOException { - NioFolder target = mock(NioFolder.class); - NioFolder parentOfTarget = mock(NioFolder.class); - Path targetPath = mock(Path.class); - when(target.fileSystem()).thenReturn(fileSystem); - when(target.path()).thenReturn(targetPath); - when(target.parent()).thenReturn((Optional) Optional.of(parentOfTarget)); - - inTest.moveTo(target); - - InOrder inOrder = inOrder(target, parentOfTarget, nioAccess); - inOrder.verify(target).delete(); - inOrder.verify(parentOfTarget).create(); - inOrder.verify(nioAccess).move(path, targetPath); - } - - } - - public class CreationTimeTests { - - @Test - public void testCreationTimeDelegatesToNioAccessCreationTimeForExistingFolder() throws IOException { - Instant exectedResult = Instant.parse("2016-01-08T19:49:00Z"); - when(nioAccess.getCreationTime(path)).thenReturn(FileTime.from(exectedResult)); - when(nioAccess.exists(path)).thenReturn(true); - when(nioAccess.isDirectory(path)).thenReturn(true); - - Instant result = inTest.creationTime().get(); - - assertThat(result, is(exectedResult)); - } - - @Test - public void testCreationTimeDelegatesToNioAccessCreationTimeForNonExistingFolder() throws IOException { - Instant exectedResult = Instant.parse("2016-01-08T19:49:00Z"); - when(nioAccess.getCreationTime(path)).thenReturn(FileTime.from(exectedResult)); - when(nioAccess.exists(path)).thenReturn(false); - - Instant result = inTest.creationTime().get(); - - assertThat(result, is(exectedResult)); - } - - @Test - public void testCreationTimeWrapsIOExceptionFromNioAccessCreationTimeInUncheckedIOException() throws IOException { - IOException exceptionFromCreationTime = new IOException(); - when(nioAccess.getCreationTime(path)).thenThrow(exceptionFromCreationTime); - when(nioAccess.exists(path)).thenReturn(true); - when(nioAccess.isDirectory(path)).thenReturn(true); - - thrown.expect(UncheckedIOException.class); - thrown.expectCause(is(exceptionFromCreationTime)); - - inTest.creationTime(); - } - - @Test - public void testCreationTimeThrowsExceptionIfFileIsNoDirectory() { - when(nioAccess.exists(path)).thenReturn(true); - when(nioAccess.isDirectory(path)).thenReturn(false); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(format("%s is a file", path)); - - inTest.creationTime(); - } - - } - - public class DeleteTests { - - @Test - public void testDeleteDoesNothingIfFolderDoesNotExist() { - when(nioAccess.isDirectory(path)).thenReturn(false); - - inTest.delete(); - } - - @Test - public void testDeleteInvokesDeleteOnChildFolderAndNioAccessDeleteAfterwards() throws IOException { - Path folderChildPath = mock(Path.class); - NioFolder folderChild = mock(NioFolder.class); - when(nioAccess.isDirectory(path)).thenReturn(true); - when(nioAccess.isDirectory(folderChildPath)).thenReturn(true); - when(instanceFactory.nioFolder(Optional.of(inTest), folderChildPath, nioAccess)).thenReturn(folderChild); - when(nioAccess.list(path)).thenAnswer(answerFrom(() -> Stream.of(folderChildPath))); - - inTest.delete(); - - InOrder inOrder = inOrder(nioAccess, folderChild); - inOrder.verify(nioAccess, times(2)).isDirectory(path); - inOrder.verify(nioAccess).list(path); - inOrder.verify(folderChild).delete(); - inOrder.verify(nioAccess).list(path); - inOrder.verify(nioAccess).delete(path); - } - - @Test - public void testDeleteInvokesDeleteOnChildFileAndNioAccessDeleteAfterwards() throws IOException { - Path fileChildPath = mock(Path.class); - NioFile fileChild = mock(NioFile.class); - when(nioAccess.isDirectory(path)).thenReturn(true); - when(nioAccess.isDirectory(fileChildPath)).thenReturn(false); - when(instanceFactory.nioFile(Optional.of(inTest), fileChildPath, nioAccess)).thenReturn(fileChild); - when(nioAccess.list(path)).thenAnswer(answerFrom(() -> Stream.of(fileChildPath))); - - inTest.delete(); - - InOrder inOrder = inOrder(nioAccess, fileChild); - inOrder.verify(nioAccess, times(2)).isDirectory(path); - inOrder.verify(nioAccess).list(path); - inOrder.verify(nioAccess).list(path); - inOrder.verify(fileChild).delete(); - inOrder.verify(nioAccess).delete(path); - } - - @Test - public void testDeleteWrapsIOExceptionFromFolderDeleteInUncheckedIOException() throws IOException { - Path fileChildPath = mock(Path.class); - NioFile fileChild = mock(NioFile.class); - WritableFile writableFile = mock(WritableFile.class); - when(fileChild.openWritable()).thenReturn(writableFile); - when(nioAccess.isDirectory(path)).thenReturn(true); - when(nioAccess.isDirectory(fileChildPath)).thenReturn(false); - when(instanceFactory.nioFile(Optional.of(inTest), fileChildPath, nioAccess)).thenReturn(fileChild); - when(nioAccess.list(path)).thenAnswer(answerFrom(() -> Stream.of(fileChildPath))); - IOException exceptionFromDelete = new IOException(); - doThrow(exceptionFromDelete).when(nioAccess).delete(path); - - thrown.expect(UncheckedIOException.class); - thrown.expectCause(is(exceptionFromDelete)); - - inTest.delete(); - } - - } - - @Test - public void testToString() { - assertThat(inTest.toString(), is(format("NioFolder(%s)", path))); - } - - private Answer answerFrom(Supplier supplier) { - return new Answer() { - @Override - public T answer(InvocationOnMock invocation) throws Throwable { - return supplier.get(); - } - }; - } - -} \ No newline at end of file diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/ReadableNioFileTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/ReadableNioFileTest.java deleted file mode 100644 index f398aa2c5..000000000 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/ReadableNioFileTest.java +++ /dev/null @@ -1,200 +0,0 @@ -package org.cryptomator.filesystem.nio; - -import static java.lang.String.format; -import static org.cryptomator.filesystem.nio.OpenMode.READ; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; - -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.nio.file.Path; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; -import org.mockito.InOrder; -import org.mockito.Mockito; - -import de.bechte.junit.runners.context.HierarchicalContextRunner; - -@RunWith(HierarchicalContextRunner.class) -public class ReadableNioFileTest { - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private Path path; - - private SharedFileChannel channel; - - private Runnable afterCloseCallback; - - private ReadableNioFile inTest; - - @Before - public void setup() { - path = mock(Path.class); - channel = mock(SharedFileChannel.class); - afterCloseCallback = mock(Runnable.class); - - inTest = new ReadableNioFile(path, channel, afterCloseCallback); - } - - @Test - public void testConstructorInvokesOpenWithReadModeOnChannelOfNioFile() { - verify(channel).open(READ); - } - - @Test - public void testReadFailsIfClosed() { - ByteBuffer irrelevant = null; - inTest.close(); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage("already closed"); - - inTest.read(irrelevant); - } - - @Test - public void testPositionFailsIfClosed() { - int irrelevant = 1; - inTest.close(); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage("already closed"); - - inTest.position(irrelevant); - } - - @Test - public void testPositionFailsIfNegative() { - - thrown.expect(IllegalArgumentException.class); - - inTest.position(-1); - } - - @Test - public void testReadDelegatesToChannelReadFullyWithZeroPositionIfNotSet() { - ByteBuffer buffer = mock(ByteBuffer.class); - - inTest.read(buffer); - - verify(channel).readFully(0, buffer); - } - - @Test - public void testReadDelegatesToChannelReadFullyWithPositionAtEndOfPreviousReadIfInvokedTwice() { - ByteBuffer buffer = mock(ByteBuffer.class); - int endOfPreviousRead = 10; - when(channel.readFully(0, buffer)).thenReturn(endOfPreviousRead); - - inTest.read(buffer); - inTest.read(buffer); - - verify(channel).readFully(0, buffer); - verify(channel).readFully(10, buffer); - } - - @Test - public void testReadDelegatesToChannelReadFullyWithPositionUnchangedIfPreviousReadReturnedEof() { - ByteBuffer buffer = mock(ByteBuffer.class); - when(channel.readFully(0, buffer)).thenReturn(SharedFileChannel.EOF); - - inTest.read(buffer); - inTest.read(buffer); - - verify(channel, times(2)).readFully(0, buffer); - } - - @Test - public void testReadDelegatesToChannelReadFullyWithSetPosition() { - ByteBuffer buffer = mock(ByteBuffer.class); - int position = 10; - inTest.position(position); - - inTest.read(buffer); - - verify(channel).readFully(position, buffer); - } - - @Test - public void testReadReturnsValueOfChannelReadFully() { - ByteBuffer buffer = mock(ByteBuffer.class); - int expectedResult = 37028; - when(channel.readFully(0, buffer)).thenReturn(expectedResult); - - int result = inTest.read(buffer); - - assertThat(result, is(expectedResult)); - } - - @Test - public void testReadDoesNotModifyBuffer() { - ByteBuffer buffer = mock(ByteBuffer.class); - - inTest.read(buffer); - - verifyZeroInteractions(buffer); - } - - @Test - public void testIsOpenReturnsTrueForNewReadableNioFile() { - assertThat(inTest.isOpen(), is(true)); - } - - @Test - public void testIsOpenReturnsFalseForClosed() { - inTest.close(); - - assertThat(inTest.isOpen(), is(false)); - } - - @Test - public void testCloseClosesChannelAndUnlocksReadLock() { - inTest.close(); - - InOrder inOrder = Mockito.inOrder(channel, afterCloseCallback); - inOrder.verify(channel).close(); - inOrder.verify(afterCloseCallback).run(); - } - - @Test - public void testCloseClosesChannelAndUnlocksReadLockOnlyOnceIfInvokedTwice() { - inTest.close(); - inTest.close(); - - InOrder inOrder = Mockito.inOrder(channel, afterCloseCallback); - inOrder.verify(channel).close(); - inOrder.verify(afterCloseCallback).run(); - } - - @Test - public void testCloseUnlocksReadLockEvenIfCloseFails() { - String message = "exceptionMessage"; - doThrow(new RuntimeException(message)).when(channel).close(); - - thrown.expectMessage(message); - - try { - inTest.close(); - } finally { - verify(afterCloseCallback).run(); - } - } - - @Test - public void testToString() { - assertThat(inTest.toString(), is(format("ReadableNioFile(%s)", path))); - } - -} diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/ReflectiveClassMatchers.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/ReflectiveClassMatchers.java deleted file mode 100644 index c5b4f2bbe..000000000 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/ReflectiveClassMatchers.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.cryptomator.filesystem.nio; - -import org.hamcrest.Description; -import org.hamcrest.Matcher; -import org.hamcrest.TypeSafeDiagnosingMatcher; - -public class ReflectiveClassMatchers { - - public static Matcher> aClassThatDoesNotDeclareMethod(String name, Class... parameterTypes) { - return new TypeSafeDiagnosingMatcher>(Class.class) { - @Override - public void describeTo(Description description) { - description // - .appendText(" a Class that does not declare method ") // - .appendText(name) // - .appendValueList("(", ", ", ")", parameterTypes); - } - - @Override - protected boolean matchesSafely(Class item, Description mismatchDescription) { - if (declaredMethodToCheck(item)) { - mismatchDescription // - .appendText(" a Class that declares method ") // - .appendText(name) // - .appendValueList("(", ", ", ")", parameterTypes); - return false; - } else { - return true; - } - } - - private boolean declaredMethodToCheck(Class item) { - try { - return item.getDeclaredMethod(name, parameterTypes) != null; - } catch (NoSuchMethodException e) { - return false; - } catch (SecurityException e) { - throw new RuntimeException(e); - } - } - }; - } - - public static Matcher> aClassThatDoesDeclareMethod(String name, Class... parameterTypes) { - return new TypeSafeDiagnosingMatcher>(Class.class) { - @Override - public void describeTo(Description description) { - description // - .appendText(" a Class that does declare method ") // - .appendText(name) // - .appendValueList("(", ", ", ")", parameterTypes); - } - - @Override - protected boolean matchesSafely(Class item, Description mismatchDescription) { - if (declaredMethodToCheck(item)) { - return true; - } else { - mismatchDescription // - .appendText(" a Class that does not declare method ") // - .appendText(name) // - .appendValueList("(", ", ", ")", parameterTypes); - return false; - } - } - - private boolean declaredMethodToCheck(Class item) { - try { - return item.getDeclaredMethod(name, parameterTypes) != null; - } catch (NoSuchMethodException e) { - return false; - } catch (SecurityException e) { - throw new RuntimeException(e); - } - } - }; - } - -} diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/SampleFilesystem.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/SampleFilesystem.java deleted file mode 100644 index cc8e230d1..000000000 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/SampleFilesystem.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.cryptomator.filesystem.nio; - -import org.apache.commons.lang3.StringUtils; - -public enum SampleFilesystem { - - EMPTY_FILESYSTEM, SOME_FOLDERS_AND_FILES - - ; - - public String directoryName() { - return StringUtils.removeEnd(name(), "_FILESYSTEM").toLowerCase(); - } - -} diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/SharedFileChannelTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/SharedFileChannelTest.java deleted file mode 100644 index af27fc61e..000000000 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/SharedFileChannelTest.java +++ /dev/null @@ -1,696 +0,0 @@ -package org.cryptomator.filesystem.nio; - -import static java.lang.String.format; -import static org.apache.commons.lang3.concurrent.ConcurrentUtils.constantFuture; -import static org.cryptomator.common.test.matcher.ExceptionMatcher.ofType; -import static org.cryptomator.filesystem.nio.SharedFileChannel.EOF; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.nio.channels.AsynchronousFileChannel; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; - -import org.cryptomator.common.Holder; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.rules.Timeout; -import org.junit.runner.RunWith; -import org.mockito.InOrder; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -import de.bechte.junit.runners.context.HierarchicalContextRunner; - -@RunWith(HierarchicalContextRunner.class) -public class SharedFileChannelTest { - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private Path path; - private NioAccess nioAccess; - - private SharedFileChannel inTest; - - @Before - public void setUp() { - path = mock(Path.class); - nioAccess = mock(NioAccess.class); - inTest = new SharedFileChannel(path, nioAccess); - } - - public class Open { - - @Test - public void testOpenFailsIfPathIsADirectory() { - when(nioAccess.isDirectory(path)).thenReturn(true); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(format("%s is a directory", path)); - - inTest.open(OpenMode.WRITE); - } - - @Test - public void testOpenFailsIfFileDoesNotExistAndOpenModeIsRead() { - when(nioAccess.isDirectory(path)).thenReturn(false); - when(nioAccess.isRegularFile(path)).thenReturn(false); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(format("%s does not exist", path)); - - inTest.open(OpenMode.READ); - } - - @Test - public void testOpenOpensAChannelIfOpenModeIsReadAndFileExists() throws IOException { - when(nioAccess.isDirectory(path)).thenReturn(false); - when(nioAccess.isRegularFile(path)).thenReturn(true); - - inTest.open(OpenMode.READ); - - verify(nioAccess).open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE); - } - - @Test - public void testOpenOpensAChannelIfOpenModeIsWriteAndFileExists() throws IOException { - when(nioAccess.isDirectory(path)).thenReturn(false); - when(nioAccess.isRegularFile(path)).thenReturn(true); - - inTest.open(OpenMode.WRITE); - - verify(nioAccess).open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE); - } - - @Test - public void testOpenOpensAChannelIfOpenModeIsWriteAndFileDoesNotExist() throws IOException { - when(nioAccess.isDirectory(path)).thenReturn(false); - when(nioAccess.isRegularFile(path)).thenReturn(false); - - inTest.open(OpenMode.WRITE); - - verify(nioAccess).open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE); - } - - @Test - public void testOpenWrapsExceptionsFromOpeningChannelInUncheckedIOExceptions() throws IOException { - when(nioAccess.isDirectory(path)).thenReturn(false); - when(nioAccess.isRegularFile(path)).thenReturn(false); - IOException exceptionFromOpeningChannel = new IOException(); - when(nioAccess.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE)).thenThrow(exceptionFromOpeningChannel); - - thrown.expect(UncheckedIOException.class); - thrown.expectCause(is(exceptionFromOpeningChannel)); - - inTest.open(OpenMode.WRITE); - } - - @Test - public void testOpenDoesNotOpenChannelTwiceIfInvokedTwiceByDifferentThreads() throws IOException { - when(nioAccess.isDirectory(path)).thenReturn(false); - when(nioAccess.isRegularFile(path)).thenReturn(false); - when(nioAccess.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE)).thenReturn(mock(AsynchronousFileChannel.class)); - - inThreadRethrowingException(() -> inTest.open(OpenMode.WRITE)); - inThreadRethrowingException(() -> inTest.open(OpenMode.WRITE)); - - verify(nioAccess).open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE); - } - - } - - public class Close { - - @Test - public void testCloseIfNotOpenFails() { - thrown.expect(IllegalStateException.class); - thrown.expectMessage("Close without corresponding open"); - - inTest.close(); - } - - @Test - public void testCloseIfClosedFails() throws IOException { - when(nioAccess.isDirectory(path)).thenReturn(false); - when(nioAccess.isRegularFile(path)).thenReturn(false); - when(nioAccess.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE)).thenReturn(mock(AsynchronousFileChannel.class)); - inTest.open(OpenMode.WRITE); - inTest.close(); - - thrown.expect(IllegalStateException.class); - thrown.expectMessage("Close without corresponding open"); - - inTest.close(); - } - - @Test - public void testCloseForcesAndClosesChannel() throws IOException { - when(nioAccess.isDirectory(path)).thenReturn(false); - when(nioAccess.isRegularFile(path)).thenReturn(false); - AsynchronousFileChannel channel = mock(AsynchronousFileChannel.class); - when(nioAccess.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE)).thenReturn(channel); - inTest.open(OpenMode.WRITE); - - inTest.close(); - - InOrder inOrder = inOrder(channel, nioAccess); - inOrder.verify(channel).force(true); - inOrder.verify(nioAccess).close(channel); - } - - @Test - public void testCloseWrapsIOExceptionFromForceInUncheckedIOExceptionAndStillClosesChannel() throws IOException { - when(nioAccess.isDirectory(path)).thenReturn(false); - when(nioAccess.isRegularFile(path)).thenReturn(false); - AsynchronousFileChannel channel = mock(AsynchronousFileChannel.class); - when(nioAccess.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE)).thenReturn(channel); - inTest.open(OpenMode.WRITE); - IOException exceptionFromForce = new IOException(); - doThrow(exceptionFromForce).when(channel).force(true); - - thrown.expect(UncheckedIOException.class); - thrown.expectCause(is(exceptionFromForce)); - - try { - inTest.close(); - } finally { - verify(nioAccess).close(channel); - } - } - - @Test - public void testCloseWrapsIOExceptionFromCloseInUncheckedIOException() throws IOException { - when(nioAccess.isDirectory(path)).thenReturn(false); - when(nioAccess.isRegularFile(path)).thenReturn(false); - AsynchronousFileChannel channel = mock(AsynchronousFileChannel.class); - when(nioAccess.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE)).thenReturn(channel); - inTest.open(OpenMode.WRITE); - IOException exceptionFromClose = new IOException(); - doThrow(exceptionFromClose).when(nioAccess).close(channel); - - thrown.expect(UncheckedIOException.class); - thrown.expectCause(is(exceptionFromClose)); - - inTest.close(); - } - - @Test - public void testCloseDoesNotCloseChannelIfOpenedTwice() throws IOException { - when(nioAccess.isDirectory(path)).thenReturn(false); - when(nioAccess.isRegularFile(path)).thenReturn(false); - AsynchronousFileChannel channel = mock(AsynchronousFileChannel.class); - when(nioAccess.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE)).thenReturn(channel); - inTest.open(OpenMode.WRITE); - inThreadRethrowingException(() -> inTest.open(OpenMode.WRITE)); - - inTest.close(); - - verify(nioAccess, never()).close(channel); - } - - @Test - public void testLastCloseDoesCloseChannelIfOpenedTwice() throws IOException { - when(nioAccess.isDirectory(path)).thenReturn(false); - when(nioAccess.isRegularFile(path)).thenReturn(false); - AsynchronousFileChannel channel = mock(AsynchronousFileChannel.class); - when(nioAccess.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE)).thenReturn(channel); - inTest.open(OpenMode.WRITE); - inThreadRethrowingException(() -> { - inTest.open(OpenMode.WRITE); - inTest.close(); - }); - - inTest.close(); - - verify(nioAccess).close(channel); - } - - } - - public class ReadFully { - - @Rule - public Timeout timeoutRule = Timeout.seconds(1); - - private AsynchronousFileChannel channel; - - @Before - public void setUp() throws IOException { - when(nioAccess.isDirectory(path)).thenReturn(false); - when(nioAccess.isRegularFile(path)).thenReturn(true); - channel = mock(AsynchronousFileChannel.class); - when(nioAccess.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE)).thenReturn(channel); - inTest.open(OpenMode.READ); - } - - @Test - public void testReadFullyWrapsExceptionFromReadInUncheckedIOException() throws InterruptedException, ExecutionException { - ByteBuffer buffer = ByteBuffer.allocate(0); - ExecutionException exceptionFromRead = new ExecutionException(new IOException()); - @SuppressWarnings("unchecked") - Future result = mock(Future.class); - when(channel.read(buffer, 0)).thenReturn(result); - when(result.get()).thenThrow(exceptionFromRead); - - thrown.expect(UncheckedIOException.class); - thrown.expectCause(is(ofType(IOException.class).withCauseThat(is(exceptionFromRead)))); - - inTest.readFully(0, buffer); - } - - @Test - public void testReadFullyDelegatesToChannelRead() throws IOException { - ByteBuffer buffer = ByteBuffer.allocate(50); - when(channel.read(buffer, 0)).thenAnswer(new Answer>() { - @Override - public Future answer(InvocationOnMock invocation) throws Throwable { - buffer.position(50); - return constantFuture(50); - } - }); - - int result = inTest.readFully(0, buffer); - - assertThat(result, is(50)); - verify(channel).read(buffer, 0); - verifyNoMoreInteractions(channel); - } - - @Test - public void testReadFullyReturnsEofWhenFirstReadReturnsIt() throws IOException { - ByteBuffer buffer = ByteBuffer.allocate(50); - when(channel.read(buffer, 0)).thenReturn(constantFuture(EOF)); - - int result = inTest.readFully(0, buffer); - - assertThat(result, is(EOF)); - verify(channel).read(buffer, 0); - verifyNoMoreInteractions(channel); - } - - @Test - public void testReadStopsReadingIfEofIsReached() throws IOException { - ByteBuffer buffer = ByteBuffer.allocate(50); - when(channel.read(buffer, 0)).thenAnswer(simulateRead(20, buffer)); - when(channel.read(buffer, 20)).thenReturn(constantFuture(EOF)); - - int result = inTest.readFully(0, buffer); - - assertThat(result, is(20)); - verify(channel).read(buffer, 0); - verify(channel).read(buffer, 20); - verifyNoMoreInteractions(channel); - } - - @Test - public void testReadFullyInvokesReadUntilBufferIsFull() throws IOException { - ByteBuffer buffer = ByteBuffer.allocate(50); - when(channel.read(buffer, 0)).then(simulateRead(20, buffer)); - when(channel.read(buffer, 20)).then(simulateRead(20, buffer)); - when(channel.read(buffer, 40)).then(simulateRead(10, buffer)); - - int result = inTest.readFully(0, buffer); - - assertThat(result, is(50)); - verify(channel).read(buffer, 0); - verify(channel).read(buffer, 20); - verify(channel).read(buffer, 40); - verifyNoMoreInteractions(channel); - } - - private Answer> simulateRead(int amount, ByteBuffer target) { - return new Answer>() { - @Override - public Future answer(InvocationOnMock invocation) throws Throwable { - target.position(target.position() + amount); - return constantFuture(amount); - } - }; - } - - } - - public class Truncate { - - private AsynchronousFileChannel channel; - - @Before - public void setUp() throws IOException { - when(nioAccess.isDirectory(path)).thenReturn(false); - when(nioAccess.isRegularFile(path)).thenReturn(true); - channel = mock(AsynchronousFileChannel.class); - when(nioAccess.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE)).thenReturn(channel); - inTest.open(OpenMode.WRITE); - } - - @Test - public void testTruncateDelegatesToChannelTruncate() throws IOException { - int truncateTo = 32; - - inTest.truncate(truncateTo); - - verify(channel).truncate(truncateTo); - } - - @Test - public void testTruncateWrapsIOExceptionInUncheckedIOException() throws IOException { - int truncateTo = 32; - IOException exceptionFromTruncate = new IOException(); - when(channel.truncate(truncateTo)).thenThrow(exceptionFromTruncate); - - thrown.expect(UncheckedIOException.class); - thrown.expectCause(is(exceptionFromTruncate)); - - inTest.truncate(truncateTo); - } - - } - - public class Size { - - private AsynchronousFileChannel channel; - - @Before - public void setUp() throws IOException { - when(nioAccess.isDirectory(path)).thenReturn(false); - when(nioAccess.isRegularFile(path)).thenReturn(true); - channel = mock(AsynchronousFileChannel.class); - when(nioAccess.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE)).thenReturn(channel); - inTest.open(OpenMode.WRITE); - } - - @Test - public void testSizeDelegatesToChannelSize() throws IOException { - long expectedSize = 832; - when(channel.size()).thenReturn(expectedSize); - - long result = inTest.size(); - - assertThat(result, is(expectedSize)); - } - - @Test - public void testSizeWrapsIOExceptionInUncheckedIOException() throws IOException { - IOException exceptionFromSize = new IOException(); - when(channel.size()).thenThrow(exceptionFromSize); - - thrown.expect(UncheckedIOException.class); - thrown.expectCause(is(exceptionFromSize)); - - inTest.size(); - } - - } - - // TODO fix / implement tests - // public class TransferTo { - // - // private AsynchronousFileChannel channel; - // - // private Path targetPath; - // private SharedFileChannel targetInTest; - // private AsynchronousFileChannel targetChannel; - // - // @Before - // public void setUp() throws IOException { - // targetPath = mock(Path.class); - // targetInTest = new SharedFileChannel(targetPath, nioAccess); - // - // when(nioAccess.isDirectory(path)).thenReturn(false); - // when(nioAccess.isRegularFile(path)).thenReturn(true); - // channel = mock(AsynchronousFileChannel.class); - // when(nioAccess.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE)).thenReturn(channel); - // inTest.open(OpenMode.WRITE); - // - // when(nioAccess.isDirectory(targetPath)).thenReturn(false); - // when(nioAccess.isRegularFile(targetPath)).thenReturn(true); - // targetChannel = mock(AsynchronousFileChannel.class); - // when(nioAccess.open(targetPath, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE)).thenReturn(targetChannel); - // targetInTest.open(OpenMode.WRITE); - // } - // - // @Test - // public void testTransferToThrowsIllegalArugmentExceptionIfCountIsNegative() { - // thrown.expect(IllegalArgumentException.class); - // thrown.expectMessage("Count must not be negative"); - // - // inTest.transferTo(0, -1, targetInTest, 0); - // } - // - // @Test - // public void testTransferToSetsPositionOfTargetChannelAndThenDelegatesToChannelsTransferTo() throws IOException { - // long targetPosition = 43L; - // long startPosition = 22L; - // long count = 39L; - // when(channel.transferTo(startPosition, count, targetChannel)).thenReturn(count); - // when(channel.size()).thenReturn(startPosition + count); - // - // long result = inTest.transferTo(startPosition, count, targetInTest, targetPosition); - // - // assertThat(result, is(count)); - // InOrder inOrder = inOrder(channel, targetChannel); - // inOrder.verify(targetChannel).position(targetPosition); - // inOrder.verify(channel).transferTo(startPosition, count, targetChannel); - // } - // - // @Test - // public void testTransferToInvokesTransferUntilAllBytesHaveBeenTransferred() throws IOException { - // long targetPosition = 43L; - // long startPosition = 22L; - // long count = 39L; - // long firstTransferCount = 10L; - // long secondTransferCount = 7L; - // long thridTransferCount = count - firstTransferCount - secondTransferCount; - // when(channel.transferTo(startPosition, count, targetChannel)).thenReturn(firstTransferCount); - // when(channel.transferTo(startPosition + firstTransferCount, count - firstTransferCount, targetChannel)).thenReturn(secondTransferCount); - // when(channel.transferTo(startPosition + firstTransferCount + secondTransferCount, thridTransferCount, targetChannel)).thenReturn(thridTransferCount); - // when(channel.size()).thenReturn(startPosition + count); - // - // long result = inTest.transferTo(startPosition, count, targetInTest, targetPosition); - // - // assertThat(result, is(count)); - // InOrder inOrder = inOrder(channel, targetChannel); - // inOrder.verify(targetChannel).position(targetPosition); - // inOrder.verify(channel).transferTo(startPosition, count, targetChannel); - // inOrder.verify(channel).transferTo(startPosition + firstTransferCount, count - firstTransferCount, targetChannel); - // inOrder.verify(channel).transferTo(startPosition + firstTransferCount + secondTransferCount, thridTransferCount, targetChannel); - // } - // - // @Test - // public void testTransferToStopsTransferAtEndOfSourceFile() throws IOException { - // long targetPosition = 43L; - // long startPosition = 22L; - // long count = 39L; - // long countAvailable = 30L; - // when(channel.transferTo(startPosition, countAvailable, targetChannel)).thenReturn(countAvailable); - // when(channel.size()).thenReturn(startPosition + countAvailable); - // - // long result = inTest.transferTo(startPosition, count, targetInTest, targetPosition); - // - // assertThat(result, is(countAvailable)); - // InOrder inOrder = inOrder(channel, targetChannel); - // inOrder.verify(targetChannel).position(targetPosition); - // inOrder.verify(channel).transferTo(startPosition, countAvailable, targetChannel); - // } - // - // @Test - // public void testTransferToWrapsIOExceptionFromPositionInUncheckedIOException() throws IOException { - // when(channel.size()).thenReturn(Long.MAX_VALUE); - // IOException exceptionFromPosition = new IOException(); - // when(targetChannel.position(anyLong())).thenThrow(exceptionFromPosition); - // - // thrown.expect(UncheckedIOException.class); - // thrown.expectCause(is(exceptionFromPosition)); - // - // inTest.transferTo(0L, 10L, targetInTest, 0L); - // } - // - // @Test - // public void testTransferToWrapsIOExceptionFromTransferToInUncheckedIOException() throws IOException { - // when(channel.size()).thenReturn(Long.MAX_VALUE); - // IOException exceptionFromTransferTo = new IOException(); - // when(channel.transferTo(anyLong(), anyLong(), any())).thenThrow(exceptionFromTransferTo); - // - // thrown.expect(UncheckedIOException.class); - // thrown.expectCause(is(exceptionFromTransferTo)); - // - // inTest.transferTo(0L, 10L, targetInTest, 0L); - // } - // - // } - - public class WriteFully { - - @Rule - public Timeout timeoutRule = Timeout.seconds(1); - - private AsynchronousFileChannel channel; - - @Before - public void setUp() throws IOException { - when(nioAccess.isDirectory(path)).thenReturn(false); - when(nioAccess.isRegularFile(path)).thenReturn(true); - channel = mock(AsynchronousFileChannel.class); - when(nioAccess.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE)).thenReturn(channel); - inTest.open(OpenMode.WRITE); - } - - @Test - public void testWriteFullyWrapsIOExceptionFromWriteIntoUncheckedIOException() throws InterruptedException, ExecutionException { - int count = 1; - int position = 0; - ByteBuffer buffer = ByteBuffer.allocate(count); - ExecutionException exceptionFromWrite = new ExecutionException(new IOException()); - @SuppressWarnings("unchecked") - Future result = mock(Future.class); - when(channel.write(buffer, position)).thenReturn(result); - when(result.get()).thenThrow(exceptionFromWrite); - - thrown.expect(UncheckedIOException.class); - thrown.expectCause(is(ofType(IOException.class).withCauseThat(is(exceptionFromWrite)))); - - inTest.writeFully(position, buffer); - } - - @Test - public void testWriteFullyDelegatesToChannelsWrite() throws IOException { - int count = 50; - int position = 31; - ByteBuffer buffer = ByteBuffer.allocate(count); - when(channel.write(buffer, position)).then(simulateWrite(count, buffer)); - - int result = inTest.writeFully(position, buffer); - - assertThat(result, is(count)); - verify(channel).write(buffer, position); - } - - @Test - public void testWriteFullyDelegatesToWriteUntilAllBytesFromBufferHaveBeenWritten() throws IOException { - int count = 50; - int countOfFirstWrite = 10; - int countOfSecondWrite = 15; - int countOfThridWrite = count - countOfFirstWrite - countOfSecondWrite; - int position = 31; - ByteBuffer buffer = ByteBuffer.allocate(count); - when(channel.write(buffer, position)).then(simulateWrite(countOfFirstWrite, buffer)); - when(channel.write(buffer, position + countOfFirstWrite)).then(simulateWrite(countOfSecondWrite, buffer)); - when(channel.write(buffer, position + countOfFirstWrite + countOfSecondWrite)).then(simulateWrite(countOfThridWrite, buffer)); - - int result = inTest.writeFully(position, buffer); - - assertThat(result, is(count)); - verify(channel).write(buffer, position); - verify(channel).write(buffer, position + countOfFirstWrite); - verify(channel).write(buffer, position + countOfFirstWrite + countOfSecondWrite); - } - - @Test - public void testWriteFullyDelegatesToWriteASingleTimeEvenIfBytesHasNotBytesRemaing() throws IOException { - int count = 0; - int position = 31; - ByteBuffer buffer = ByteBuffer.allocate(count); - when(channel.write(buffer, position)).then(simulateWrite(count, buffer)); - - int result = inTest.writeFully(position, buffer); - - assertThat(result, is(count)); - verify(channel).write(buffer, position); - } - - private Answer> simulateWrite(int amount, ByteBuffer target) { - return new Answer>() { - @Override - public Future answer(InvocationOnMock invocation) throws Throwable { - target.position(target.position() + amount); - return constantFuture(amount); - } - }; - } - - } - - public class OperationsFailingIfClosed { - - @Test - public void testReadFullyFailsIfNotOpen() { - ByteBuffer irrelevant = null; - - thrown.expect(IllegalStateException.class); - thrown.expectMessage("SharedFileChannel is not open"); - - inTest.readFully(0, irrelevant); - } - - @Test - public void testTruncateFailsIfNotOpen() { - thrown.expect(IllegalStateException.class); - thrown.expectMessage("SharedFileChannel is not open"); - - inTest.truncate(0); - } - - @Test - public void testSizeFailsIfNotOpen() { - thrown.expect(IllegalStateException.class); - thrown.expectMessage("SharedFileChannel is not open"); - - inTest.size(); - } - - @Test - public void testWriteFullyFailsIfNotOpen() { - ByteBuffer irrelevant = null; - - thrown.expect(IllegalStateException.class); - thrown.expectMessage("SharedFileChannel is not open"); - - inTest.writeFully(0, irrelevant); - } - - } - - private void inThreadRethrowingException(Runnable task) { - Holder exception = new Holder<>(null); - Thread thread = new Thread(() -> { - try { - task.run(); - } catch (Throwable e) { - exception.set(e); - } - }); - thread.start(); - try { - thread.join(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - rethrowUnchecked(exception.get()); - } - - private void rethrowUnchecked(Throwable exception) { - if (exception instanceof RuntimeException) { - throw (RuntimeException) exception; - } else if (exception instanceof Error) { - throw (Error) exception; - } else if (exception != null) { - throw new RuntimeException(exception); - } - } - -} diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/ThreadStackMatcher.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/ThreadStackMatcher.java deleted file mode 100644 index 5185f4797..000000000 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/ThreadStackMatcher.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.cryptomator.filesystem.nio; - -import org.hamcrest.Description; -import org.hamcrest.Matcher; -import org.hamcrest.TypeSafeDiagnosingMatcher; - -class ThreadStackMatcher extends TypeSafeDiagnosingMatcher { - - public static Matcher stackContains(Class type, String methodName) { - return new ThreadStackMatcher(type, methodName); - } - - private final Class type; - private final String methodName; - - private ThreadStackMatcher(Class type, String methodName) { - super(Thread.class); - this.type = type; - this.methodName = methodName; - } - - @Override - public void describeTo(Description description) { - description.appendText("a thread executing ") // - .appendValue(type) // - .appendText(".") // - .appendText(methodName); - } - - @Override - protected boolean matchesSafely(Thread item, Description mismatchDescription) { - StackTraceElement[] stack = item.getStackTrace(); - if (!containsNeededStackTraceElement(stack)) { - mismatchDescription.appendText("a thread not executing ") // - .appendValue(type) // - .appendText(".") // - .appendText(methodName); - return false; - } else { - return true; - } - } - - private boolean containsNeededStackTraceElement(StackTraceElement[] stack) { - for (StackTraceElement stackTraceElement : stack) { - if (isNeededStackTraceElement(stackTraceElement)) { - return true; - } - } - return false; - } - - private boolean isNeededStackTraceElement(StackTraceElement stackTraceElement) { - return type.getName().equals(stackTraceElement.getClassName()) // - && methodName.equals(stackTraceElement.getMethodName()); - } - -} diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/WritableNioFileTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/WritableNioFileTest.java deleted file mode 100644 index ab9ebd0b1..000000000 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/WritableNioFileTest.java +++ /dev/null @@ -1,289 +0,0 @@ -package org.cryptomator.filesystem.nio; - -import static java.lang.String.format; -import static org.cryptomator.filesystem.nio.OpenMode.WRITE; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; - -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.nio.file.Path; - -import org.cryptomator.filesystem.FileSystem; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; -import org.mockito.InOrder; - -import de.bechte.junit.runners.context.HierarchicalContextRunner; - -@RunWith(HierarchicalContextRunner.class) -public class WritableNioFileTest { - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private FileSystem fileSystem; - - private Path path; - - private SharedFileChannel channel; - - private Runnable afterCloseCallback; - - private WritableNioFile inTest; - - @Before - public void setup() { - fileSystem = mock(FileSystem.class); - channel = mock(SharedFileChannel.class); - path = mock(Path.class); - afterCloseCallback = mock(Runnable.class); - inTest = new WritableNioFile(fileSystem, path, channel, afterCloseCallback); - } - - public class ConstructorTests { - - @Test - public void testConstructorSetsFileSystem() { - assertThat(inTest.fileSystem(), is(fileSystem)); - } - - @Test - public void testConstructorSetsPath() { - assertThat(inTest.path(), is(path)); - } - - @Test - public void testConstructorSetsChannel() { - assertThat(inTest.channel(), is(channel)); - } - - } - - public class WriteTests { - - @Test - public void testWriteOpensChannelIfClosedBeforeInvokingWriteFully() { - ByteBuffer irrelevant = null; - - inTest.write(irrelevant); - - InOrder inOrder = inOrder(channel); - inOrder.verify(channel).open(WRITE); - inOrder.verify(channel).writeFully(0, irrelevant); - } - - @Test - public void testWriteDoesNotModifyBuffer() { - ByteBuffer buffer = mock(ByteBuffer.class); - - inTest.write(buffer); - - verifyZeroInteractions(buffer); - } - - @Test - public void testWriteInvokesWriteFullyWithZeroPositionIfNotSet() { - ByteBuffer buffer = mock(ByteBuffer.class); - - inTest.write(buffer); - - verify(channel).writeFully(0, buffer); - } - - @Test - public void testWriteInvokesWriteFullyWithSetPosition() { - ByteBuffer buffer = mock(ByteBuffer.class); - long position = 10; - inTest.position(position); - - inTest.write(buffer); - - verify(channel).writeFully(position, buffer); - } - - @Test - public void testWriteInvokesWriteFullyWithEndOfPreviousWriteIfInvokedTwice() { - ByteBuffer buffer = mock(ByteBuffer.class); - int endOfPreviousWrite = 10; - when(channel.writeFully(0, buffer)).thenReturn(endOfPreviousWrite); - - inTest.write(buffer); - inTest.write(buffer); - - verify(channel).writeFully(endOfPreviousWrite, buffer); - } - - @Test - public void testWriteReturnsResultOfWriteFully() { - ByteBuffer buffer = mock(ByteBuffer.class); - int resultOfWriteFully = 14; - when(channel.writeFully(0, buffer)).thenReturn(resultOfWriteFully); - - int result = inTest.write(buffer); - - assertThat(result, is(resultOfWriteFully)); - } - - } - - public class TruncateTests { - - @Test - public void testTruncateInvokesChannelsOpenWithModeWriteIfInvokedForTheFirstTimeBeforeInvokingTruncate() { - inTest.truncate(); - - InOrder inOrder = inOrder(channel); - inOrder.verify(channel).open(WRITE); - inOrder.verify(channel).truncate(anyInt()); - } - - @Test - public void testTruncateChannelsTruncateWithZeroAsParameter() { - inTest.truncate(); - - verify(channel).truncate(0); - } - - } - - public class CloseTests { - - @Test - public void testCloseClosesChannelIfOpenedAndInvokesAfterCloseCallback() { - inTest.truncate(); - - inTest.close(); - - InOrder inOrder = inOrder(channel, afterCloseCallback); - inOrder.verify(channel).close(); - inOrder.verify(afterCloseCallback).run(); - } - - @Test - public void testCloseDoesNothingOnSecondInvocation() { - inTest.truncate(); - - inTest.close(); - inTest.close(); - - InOrder inOrder = inOrder(channel, afterCloseCallback); - inOrder.verify(channel).close(); - verify(afterCloseCallback).run(); - } - - @Test - public void testCloseInvokesAfterCloseCallbackEvenIfCloseThrowsException() { - inTest.truncate(); - String message = "exceptionMessage"; - doThrow(new RuntimeException(message)).when(channel).close(); - - thrown.expectMessage(message); - - try { - inTest.close(); - } finally { - verify(afterCloseCallback).run(); - } - } - - } - - public class IsOpenTests { - - @Test - public void testIsOpenReturnsTrueForNewInstance() { - assertThat(inTest.isOpen(), is(true)); - } - - @Test - public void testIsOpenReturnsFalseForClosedInstance() { - inTest.close(); - - assertThat(inTest.isOpen(), is(false)); - } - - } - - public class OperationsFailIfClosedTests { - - @Test - public void testWriteFailsIfClosed() { - inTest.close(); - ByteBuffer irrelevant = null; - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage("already closed"); - - inTest.write(irrelevant); - } - - @Test - public void testPositionFailsIfClosed() { - inTest.close(); - long irrelevant = 1023; - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage("already closed"); - - inTest.position(irrelevant); - } - - @Test - public void testTruncateFailsIfClosed() { - inTest.close(); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage("already closed"); - - inTest.truncate(); - } - - } - - @Test - public void testEnsureChannelIsOpenedInvokesChannelOpenWithModeWrite() { - inTest.ensureChannelIsOpened(); - - verify(channel).open(WRITE); - } - - @Test - public void testEnsureChannelIsOpenedInvokesChannelOpenWithModeWriteOnlyOnceIfInvokedTwice() { - inTest.ensureChannelIsOpened(); - inTest.ensureChannelIsOpened(); - - verify(channel).open(WRITE); - } - - @Test - public void testCloseChannelIfOpenInvokesChannelsCloseIfOpenedEarlier() { - inTest.ensureChannelIsOpened(); - inTest.closeChannelIfOpened(); - - verify(channel).close(); - } - - @Test - public void testCloseChannelIfOpenDoesNotInvokeChannelsCloseIfNotOpenedEarlier() { - inTest.closeChannelIfOpened(); - - verifyZeroInteractions(channel); - } - - @Test - public void testToString() { - assertThat(inTest.toString(), is(format("WritableNioFile(%s)", path))); - } - -} diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/FilesystemSetupUtils.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/FilesystemSetupUtils.java deleted file mode 100644 index 2bb7889a8..000000000 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/FilesystemSetupUtils.java +++ /dev/null @@ -1,112 +0,0 @@ -package org.cryptomator.filesystem.nio.integrationtests; - -import static org.cryptomator.common.test.TempFilesRemovedOnShutdown.createTempDirectory; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.UncheckedIOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.attribute.FileTime; -import java.time.Instant; - -import org.apache.commons.io.IOUtils; - -class FilesystemSetupUtils { - - public static Path emptyFilesystem() { - try { - return createTempDirectory("test-filesystem"); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public static Path testFilesystem(Entry firstEntry, Entry... entries) { - try { - Path root = createTempDirectory("test-filesystem"); - firstEntry.create(root); - for (Entry entry : entries) { - entry.create(root); - } - return root; - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public static FileEntry file(String path) { - return new FileEntry(Paths.get(path)); - } - - public static FolderEntry folder(String path) { - return new FolderEntry(Paths.get(path)); - } - - interface Entry { - - void create(Path root) throws IOException; - - } - - public static class FileEntry implements Entry { - private Path relativePath; - private byte[] data = new byte[0]; - private Instant lastModified; - - public FileEntry(Path relativePath) { - this.relativePath = relativePath; - } - - public FileEntry withData(byte[] data) { - this.data = data; - return this; - } - - public FileEntry withData(String data) { - return withData(data.getBytes()); - } - - public FileEntry withLastModified(Instant lastModified) { - this.lastModified = lastModified; - return this; - } - - @Override - public void create(Path root) throws IOException { - Path filePath = root.resolve(relativePath); - Files.createDirectories(filePath.getParent()); - try (OutputStream out = Files.newOutputStream(filePath)) { - IOUtils.write(data, out); - } - if (lastModified != null) { - Files.setLastModifiedTime(filePath, FileTime.from(lastModified)); - } - } - } - - public static class FolderEntry implements Entry { - private Path relativePath; - private Instant lastModified; - - public FolderEntry(Path relativePath) { - this.relativePath = relativePath; - } - - public FolderEntry withLastModified(Instant lastModified) { - this.lastModified = lastModified; - return this; - } - - @Override - public void create(Path root) throws IOException { - Path path = root.resolve(relativePath); - Files.createDirectories(path); - if (lastModified != null) { - Files.setLastModifiedTime(path, FileTime.from(lastModified)); - } - } - } - -} diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioFileIntegrationTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioFileIntegrationTest.java deleted file mode 100644 index c64594432..000000000 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioFileIntegrationTest.java +++ /dev/null @@ -1,263 +0,0 @@ -package org.cryptomator.filesystem.nio.integrationtests; - -import static org.cryptomator.filesystem.nio.integrationtests.FilesystemSetupUtils.emptyFilesystem; -import static org.cryptomator.filesystem.nio.integrationtests.FilesystemSetupUtils.file; -import static org.cryptomator.filesystem.nio.integrationtests.FilesystemSetupUtils.folder; -import static org.cryptomator.filesystem.nio.integrationtests.FilesystemSetupUtils.testFilesystem; -import static org.cryptomator.filesystem.nio.integrationtests.PathMatcher.doesNotExist; -import static org.cryptomator.filesystem.nio.integrationtests.PathMatcher.isFile; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; - -import java.io.UncheckedIOException; -import java.nio.file.Path; -import java.time.Instant; - -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.nio.NioFileSystem; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -public class NioFileIntegrationTest { - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @Test - public void testExistsForExistingFile() { - File existingFile = NioFileSystem.rootedAt(testFilesystem(file("testFile"))) // - .file("testFile"); - - assertThat(existingFile.exists(), is(true)); - } - - @Test - public void testExistsForNonExistingFile() { - File nonExistingFile = NioFileSystem.rootedAt(emptyFilesystem()) // - .file("testFile"); - - assertThat(nonExistingFile.exists(), is(false)); - } - - @Test - public void testExistsForFileWhichIsAFolder() { - File fileWhichIsAFolder = NioFileSystem.rootedAt(testFilesystem(folder("nameOfAnExistingFolder"))) // - .file("nameOfAnExistingFolder"); - - assertThat(fileWhichIsAFolder.exists(), is(false)); - } - - @Test - public void testLastModifiedForExistingFile() { - Instant expectedLastModified = Instant.parse("2015-12-31T15:03:34Z"); - File existingFile = NioFileSystem - .rootedAt(testFilesystem( // - file("testFile").withLastModified(expectedLastModified))) // - .file("testFile"); - - assertThat(existingFile.lastModified(), is(expectedLastModified)); - } - - @Test - public void testLastModifiedForNonExistingFile() { - Path filesystemPath = emptyFilesystem(); - Path pathOfNonExistingFile = filesystemPath.resolve("nonExistingFile"); - File nonExistingFile = NioFileSystem.rootedAt(filesystemPath) // - .file("nonExistingFile"); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(pathOfNonExistingFile.toString()); - - nonExistingFile.lastModified(); - } - - @Test - public void testLastModifiedForFileWhichIsAFolder() { - Path filesystemPath = testFilesystem(folder("nameOfAnExistingFolder")); - Path pathOfNonExistingFile = filesystemPath.resolve("nameOfAnExistingFolder"); - File fileWhichIsAFolder = NioFileSystem.rootedAt(filesystemPath) // - .file("nameOfAnExistingFolder"); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(pathOfNonExistingFile.toString()); - - fileWhichIsAFolder.lastModified(); - } - - @Test - public void testCopyToNonExistingTarget() { - Path filesystemPath = testFilesystem(file("sourceFile").withData("fileContents")); - FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); - Path sourceFilePath = filesystemPath.resolve("sourceFile"); - Path targetFilePath = filesystemPath.resolve("targetFile"); - File source = fileSystem.file("sourceFile"); - File target = fileSystem.file("targetFile"); - - source.copyTo(target); - - assertThat(sourceFilePath, isFile().withContent("fileContents")); - assertThat(targetFilePath, isFile().withContent("fileContents")); - } - - @Test - public void testCopyToExistingTarget() { - Path filesystemPath = testFilesystem( // - file("sourceFile").withData("fileContents"), // - file("targetFile").withData("wrongFileContents")); - FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); - Path sourceFilePath = filesystemPath.resolve("sourceFile"); - Path targetFilePath = filesystemPath.resolve("targetFile"); - File source = fileSystem.file("sourceFile"); - File target = fileSystem.file("targetFile"); - - source.copyTo(target); - - assertThat(sourceFilePath, isFile().withContent("fileContents")); - assertThat(targetFilePath, isFile().withContent("fileContents")); - } - - @Test - public void testCopyToFolderTarget() { - Path filesystemPath = testFilesystem( // - file("sourceFile").withData("fileContents"), // - folder("aFolderName")); - FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); - Path targetFilePath = filesystemPath.resolve("aFolderName").toAbsolutePath(); - File source = fileSystem.file("sourceFile"); - File target = fileSystem.file("aFolderName"); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(targetFilePath.toAbsolutePath().toString()); - - source.copyTo(target); - } - - @Test - public void testCopyToOfNonExistingFile() { - Path filesystemPath = emptyFilesystem(); - Path filePath = filesystemPath.resolve("nonExistingFile").toAbsolutePath(); - FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); - File nonExistingFile = fileSystem.file("nonExistingFile"); - File target = fileSystem.file("target"); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(filePath.toString()); - - nonExistingFile.copyTo(target); - } - - @Test - public void testCopyToOfFileWhichIsAFolder() { - Path filesystemPath = testFilesystem(folder("folderName")); - Path filePath = filesystemPath.resolve("folderName").toAbsolutePath(); - FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); - File fileWhichIsAFolder = fileSystem.file("folderName"); - File target = fileSystem.file("target"); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(filePath.toString()); - - fileWhichIsAFolder.copyTo(target); - } - - @Test - public void testMoveToNonExistingTarget() { - Path filesystemPath = testFilesystem(file("sourceFile").withData("fileContents")); - FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); - Path sourceFilePath = filesystemPath.resolve("sourceFile"); - Path targetFilePath = filesystemPath.resolve("targetFile"); - File source = fileSystem.file("sourceFile"); - File target = fileSystem.file("targetFile"); - - source.moveTo(target); - - assertThat(sourceFilePath, doesNotExist()); - assertThat(targetFilePath, isFile().withContent("fileContents")); - } - - @Test - public void testMoveToExistingTarget() { - Path filesystemPath = testFilesystem( // - file("sourceFile").withData("fileContents"), // - file("targetFile").withData("wrongFileContents")); - FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); - Path sourceFilePath = filesystemPath.resolve("sourceFile"); - Path targetFilePath = filesystemPath.resolve("targetFile"); - File source = fileSystem.file("sourceFile"); - File target = fileSystem.file("targetFile"); - - source.moveTo(target); - - assertThat(sourceFilePath, doesNotExist()); - assertThat(targetFilePath, isFile().withContent("fileContents")); - } - - @Test - public void testMoveToSameFile() { - Path filesystemPath = testFilesystem(file("fileName").withData("fileContents")); - Path filePath = filesystemPath.resolve("fileName"); - File file = NioFileSystem.rootedAt(filesystemPath).file("fileName"); - - file.moveTo(file); - - assertThat(filePath, isFile().withContent("fileContents")); - } - - @Test - public void testMoveToDirectoryTarget() { - Path filesystemPath = testFilesystem( // - file("sourceFile").withData("fileContents"), // - folder("aFolderName")); - FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); - Path targetFilePath = filesystemPath.resolve("aFolderName").toAbsolutePath(); - File source = fileSystem.file("sourceFile"); - File target = fileSystem.file("aFolderName"); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(targetFilePath.toAbsolutePath().toString()); - - source.moveTo(target); - } - - @Test - public void testMoveToOfNonExistingFile() { - Path filesystemPath = emptyFilesystem(); - Path filePath = filesystemPath.resolve("nonExistingFile").toAbsolutePath(); - FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); - File nonExistingFile = fileSystem.file("nonExistingFile"); - File target = fileSystem.file("target"); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(filePath.toString()); - - nonExistingFile.moveTo(target); - } - - @Test - public void testMoveToOfFileWhichIsAFolder() { - Path filesystemPath = testFilesystem(folder("folderName")); - Path filePath = filesystemPath.resolve("folderName").toAbsolutePath(); - FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); - File fileWhichIsAFolder = fileSystem.file("folderName"); - File target = fileSystem.file("target"); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(filePath.toString()); - - fileWhichIsAFolder.moveTo(target); - } - - @Test - public void testOpenWritableDoesNotCreateANonExisitingFile() { - Path filesystemPath = emptyFilesystem(); - Path filePath = filesystemPath.resolve("nonExistingFile"); - File file = NioFileSystem.rootedAt(filesystemPath).file("nonExistingFile"); - - file.openWritable(); - - assertThat(filePath, doesNotExist()); - } - -} \ No newline at end of file diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioFileSystemIntegrationTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioFileSystemIntegrationTest.java deleted file mode 100644 index 177a6f95f..000000000 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioFileSystemIntegrationTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.cryptomator.filesystem.nio.integrationtests; - -import static org.cryptomator.filesystem.nio.integrationtests.FilesystemSetupUtils.emptyFilesystem; -import static org.cryptomator.filesystem.nio.integrationtests.FilesystemSetupUtils.file; -import static org.cryptomator.filesystem.nio.integrationtests.FilesystemSetupUtils.testFilesystem; -import static org.cryptomator.filesystem.nio.integrationtests.PathMatcher.isDirectory; -import static org.junit.Assert.assertThat; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Files; -import java.nio.file.Path; - -import org.cryptomator.filesystem.nio.NioFileSystem; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -public class NioFileSystemIntegrationTest { - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @Test - public void testObtainingAFileSystemWithNonExistingRootCreatesItIncludingAllParentFolders() throws IOException { - Path emptyFilesystem = emptyFilesystem(); - Path nonExistingFilesystemRoot = emptyFilesystem.resolve("nonExistingRoot"); - Files.delete(emptyFilesystem); - - NioFileSystem.rootedAt(nonExistingFilesystemRoot); - - assertThat(nonExistingFilesystemRoot, isDirectory()); - } - - @Test - public void testObtainingAFileSystemWhooseRootIsAFileFails() throws IOException { - Path emptyFilesystem = testFilesystem(file("rootWhichIsAFile")); - Path rootWhichIsAFile = emptyFilesystem.resolve("rootWhichIsAFile"); - - thrown.expect(UncheckedIOException.class); - - NioFileSystem.rootedAt(rootWhichIsAFile); - } -} \ No newline at end of file diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioFolderIntegrationTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioFolderIntegrationTest.java deleted file mode 100644 index aec85a67a..000000000 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioFolderIntegrationTest.java +++ /dev/null @@ -1,347 +0,0 @@ -package org.cryptomator.filesystem.nio.integrationtests; - -import static java.util.stream.Collectors.toList; -import static org.cryptomator.common.test.matcher.ContainsMatcher.containsInAnyOrder; -import static org.cryptomator.filesystem.nio.integrationtests.FilesystemSetupUtils.emptyFilesystem; -import static org.cryptomator.filesystem.nio.integrationtests.FilesystemSetupUtils.file; -import static org.cryptomator.filesystem.nio.integrationtests.FilesystemSetupUtils.folder; -import static org.cryptomator.filesystem.nio.integrationtests.FilesystemSetupUtils.testFilesystem; -import static org.cryptomator.filesystem.nio.integrationtests.NioNodeMatcher.fileWithName; -import static org.cryptomator.filesystem.nio.integrationtests.NioNodeMatcher.folderWithName; -import static org.cryptomator.filesystem.nio.integrationtests.PathMatcher.doesNotExist; -import static org.cryptomator.filesystem.nio.integrationtests.PathMatcher.isDirectory; -import static org.cryptomator.filesystem.nio.integrationtests.PathMatcher.isFile; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.Matchers.empty; -import static org.junit.Assert.assertThat; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.Instant; - -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.nio.NioFileSystem; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -public class NioFolderIntegrationTest { - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @Test - public void testCreateExistingFolder() throws IOException { - String folderName = "nameOfFolder"; - Path testFilesystemPath = testFilesystem( // - folder(folderName)); - NioFileSystem fileSystem = NioFileSystem.rootedAt(testFilesystemPath); - Folder existingFolder = fileSystem.folder(folderName); - - existingFolder.create(); - - assertThat(Files.isDirectory(testFilesystemPath.resolve(folderName)), is(true)); - } - - @Test - public void testCreateNonExistingFolder() throws IOException { - String folderName = "nameOfFolder"; - Path testFilesystemPath = emptyFilesystem(); - NioFileSystem fileSystem = NioFileSystem.rootedAt(testFilesystemPath); - Folder nonExistingFolder = fileSystem.folder(folderName); - - nonExistingFolder.create(); - - assertThat(Files.isDirectory(testFilesystemPath.resolve(folderName)), is(true)); - } - - @Test - public void testCreateFolderWithMissingParent() throws IOException { - String parentFolderName = "nameOfParentFolder"; - String folderName = "nameOfFolder"; - Path emptyFilesystemPath = emptyFilesystem(); - NioFileSystem fileSystem = NioFileSystem.rootedAt(emptyFilesystemPath); - Folder folderWithNonExistingParent = fileSystem.folder(parentFolderName).folder(folderName); - - folderWithNonExistingParent.create(); - - assertThat(Files.isDirectory(emptyFilesystemPath.resolve(parentFolderName).resolve(folderName)), is(true)); - } - - @Test - public void testCreateFolderWhichIsAFile() throws IOException { - String folderName = "nameOfFolder"; - Path testFilesystemPath = testFilesystem(file(folderName)); - NioFileSystem fileSystem = NioFileSystem.rootedAt(testFilesystemPath); - Folder folderWhichIsAFile = fileSystem.folder(folderName); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(testFilesystemPath.resolve(folderName).toString()); - - folderWhichIsAFile.create(); - } - - @Test - public void testChildrenOfEmptyFolder() throws IOException { - Folder folder = NioFileSystem.rootedAt(emptyFilesystem()); - - assertThat(folder.children().collect(toList()), is(empty())); - } - - @Test - public void testChildrenOfNonExistingFolder() throws IOException { - String nameOfNonExistingFolder = "nameOfFolder"; - Path filesystemPath = emptyFilesystem(); - Folder folder = NioFileSystem.rootedAt(filesystemPath).folder(nameOfNonExistingFolder); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(filesystemPath.resolve(nameOfNonExistingFolder).toString()); - - folder.children(); - } - - @Test - public void testChildrenOfFolderWhichIsAFile() throws IOException { - String folderName = "nameOfFolder"; - Path filesystemPath = testFilesystem(file(folderName)); - Folder folder = NioFileSystem.rootedAt(filesystemPath).folder(folderName); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(filesystemPath.resolve(folderName).toString()); - - folder.children(); - } - - @Test - public void testChildrenOfFolder() throws IOException { - String folderName1 = "folder1"; - String folderName2 = "folder2"; - String fileName1 = "file1"; - String fileName2 = "file2"; - - Folder folder = NioFileSystem.rootedAt(testFilesystem( // - folder(folderName1), // - folder(folderName2), // - file(fileName1), // - file(fileName2))); - - assertThat(folder.children().collect(toList()), - containsInAnyOrder( // - folderWithName(folderName1), // - folderWithName(folderName2), // - fileWithName(fileName1), // - fileWithName(fileName2))); - } - - @Test - public void testDeleteOfEmptyFolder() throws IOException { - String folderName = "nameOfFolder"; - Path filesystemPath = testFilesystem( // - folder(folderName)); - Folder folder = NioFileSystem.rootedAt(filesystemPath).folder(folderName); - - folder.delete(); - - assertThat(filesystemPath.resolve(folderName), doesNotExist()); - } - - @Test - public void testDeleteOfFolderWithChildren() throws IOException { - String folderName = "nameOfFolder"; - Path filesystemPath = testFilesystem( // - folder(folderName), // - folder(folderName + "/subfolder1"), // - file(folderName + "/subfolder1/fileName1"), // - folder(folderName + "/subfolder2"), // - file(folderName + "/fileName1"), // - file(folderName + "/fileName2")); - Folder folder = NioFileSystem.rootedAt(filesystemPath).folder(folderName); - - folder.delete(); - - assertThat(filesystemPath.resolve(folderName), doesNotExist()); - } - - @Test - public void testDeleteOfNonExistingFolder() throws IOException { - String folderName = "nameOfFolder"; - Path filesystemPath = emptyFilesystem(); - Folder nonExistingFolder = NioFileSystem.rootedAt(filesystemPath).folder(folderName); - - nonExistingFolder.delete(); - - assertThat(filesystemPath.resolve(folderName), doesNotExist()); - } - - @Test - public void testDeleteOfFolderWhichIsAFile() throws IOException { - String folderName = "nameOfFolder"; - Path filesystemPath = testFilesystem( // - file(folderName)); - Folder folder = NioFileSystem.rootedAt(filesystemPath).folder(folderName); - - folder.delete(); - - assertThat(filesystemPath.resolve(folderName), isFile()); - } - - @Test - public void testExistsForExistingFolder() throws IOException { - String folderName = "nameOfFolder"; - Path filesystemPath = testFilesystem( // - folder(folderName)); - Folder folder = NioFileSystem.rootedAt(filesystemPath).folder(folderName); - - assertThat(folder.exists(), is(true)); - } - - @Test - public void testExistsForNonExistingFolder() throws IOException { - String folderName = "nameOfFolder"; - Path filesystemPath = emptyFilesystem(); - Folder nonExistingFolder = NioFileSystem.rootedAt(filesystemPath).folder(folderName); - - assertThat(nonExistingFolder.exists(), is(false)); - } - - @Test - public void testExistsForFolderWhichIsAFile() throws IOException { - String folderName = "nameOfFolder"; - Path filesystemPath = testFilesystem( // - file(folderName)); - Folder folder = NioFileSystem.rootedAt(filesystemPath).folder(folderName); - - assertThat(folder.exists(), is(false)); - } - - @Test - public void testLastModifiedOfExistingFolder() throws IOException { - String folderName = "nameOfFolder"; - Instant lastModified = Instant.parse("2015-12-29T15:36:10.00Z"); - Folder folder = NioFileSystem - .rootedAt(testFilesystem( // - folder(folderName).withLastModified(lastModified))) // - .folder(folderName); - - assertThat(folder.lastModified(), is(lastModified)); - } - - @Test - public void testLastModifiedOfNonExistingFolder() throws IOException { - String folderName = "nameOfFolder"; - Path filesystemPath = emptyFilesystem(); - Folder folder = NioFileSystem.rootedAt(filesystemPath).folder(folderName); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(filesystemPath.resolve(folderName).toString()); - - folder.lastModified(); - } - - @Test - public void testLastModifiedOfFolderWhichIsAFile() throws IOException { - String folderName = "nameOfFolder"; - Path filesystemPath = testFilesystem(file(folderName)); - Folder folder = NioFileSystem // - .rootedAt(filesystemPath) // - .folder(folderName); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(filesystemPath.resolve(folderName).toString()); - - folder.lastModified(); - } - - @Test - public void testMoveToOfFolderToNonExistingFolder() throws IOException { - Path filesystemPath = testFilesystem( // - folder("folderToMove")); - NioFileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); - Folder folderToMove = fileSystem.folder("folderToMove"); - Folder folderToMoveTo = fileSystem.folder("folderToMoveTo"); - - folderToMove.moveTo(folderToMoveTo); - - assertThat(filesystemPath.resolve("folderToMove"), doesNotExist()); - assertThat(filesystemPath.resolve("folderToMoveTo"), isDirectory()); - } - - @Test - public void testMoveToOfFolderWithChildren() throws IOException { - Path filesystemPath = testFilesystem( // - folder("folderToMove"), // - folder("folderToMove/subfolder1"), // - folder("folderToMove/subfolder2"), // - file("folderToMove/subfolder1/file1").withData("dataOfFile1"), // - file("folderToMove/file2").withData("dataOfFile2"), // - file("folderToMove/file3").withData("dataOfFile3")); - NioFileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); - Folder folderToMove = fileSystem.folder("folderToMove"); - Folder folderToMoveTo = fileSystem.folder("folderToMoveTo"); - - folderToMove.moveTo(folderToMoveTo); - - assertThat(filesystemPath.resolve("folderToMove"), doesNotExist()); - assertThat(filesystemPath.resolve("folderToMoveTo"), isDirectory()); - assertThat(filesystemPath.resolve("folderToMoveTo/subfolder1"), isDirectory()); - assertThat(filesystemPath.resolve("folderToMoveTo/subfolder2"), isDirectory()); - assertThat(filesystemPath.resolve("folderToMoveTo/subfolder1/file1"), isFile().withContent("dataOfFile1")); - assertThat(filesystemPath.resolve("folderToMoveTo/file2"), isFile().withContent("dataOfFile2")); - assertThat(filesystemPath.resolve("folderToMoveTo/file3"), isFile().withContent("dataOfFile3")); - } - - @Test - public void testMoveToOfFolderToExistingFolder() throws IOException { - Path filesystemPath = testFilesystem( // - folder("folderToMove"), // - file("folderToMove/file1").withData("dataOfFile1"), // - file("folderToMove/file2").withData("dataOfFile2"), // - folder("folderToMoveTo"), // - file("folderToMoveTo/file1").withData("wrongDataOfFile1"), // - file("folderToMoveTo/fileWhichShouldNotExistAfterMove")); - NioFileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); - Folder folderToMove = fileSystem.folder("folderToMove"); - Folder folderToMoveTo = fileSystem.folder("folderToMoveTo"); - - folderToMove.moveTo(folderToMoveTo); - - assertThat(filesystemPath.resolve("folderToMove"), doesNotExist()); - assertThat(filesystemPath.resolve("folderToMoveTo"), isDirectory()); - assertThat(filesystemPath.resolve("folderToMoveTo/file1"), isFile().withContent("dataOfFile1")); - assertThat(filesystemPath.resolve("folderToMoveTo/file2"), isFile().withContent("dataOfFile2")); - assertThat(filesystemPath.resolve("folderToMoveTo/fileWhichShouldNotExistAfterMove"), doesNotExist()); - } - - @Test - public void testMoveToOfFolderToExistingFile() throws IOException { - Path filesystemPath = testFilesystem( // - folder("folderToMove"), // - file("folderToMoveTo")); - FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath); - Folder folderToMove = fileSystem.folder("folderToMove"); - Folder folderToMoveTo = fileSystem.folder("folderToMoveTo"); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage(filesystemPath.resolve("folderToMoveTo").toString()); - - folderToMove.moveTo(folderToMoveTo); - } - - @Test - public void testMoveToOfFolderToFolderOfAnotherFileSystem() throws IOException { - Path filesystemPath = testFilesystem(folder("folderToMove")); - Folder folderToMove = NioFileSystem.rootedAt(filesystemPath).folder("folderToMove"); - Folder folderToMoveTo = NioFileSystem.rootedAt(filesystemPath).folder("folderToMoveTo"); - - thrown.expect(IllegalArgumentException.class); - - folderToMove.moveTo(folderToMoveTo); - - assertThat(filesystemPath.resolve("folderToMove"), isDirectory()); - assertThat(filesystemPath.resolve("folderToMoveTo"), doesNotExist()); - } - -} \ No newline at end of file diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioNodeMatcher.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioNodeMatcher.java deleted file mode 100644 index 6a016f978..000000000 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/NioNodeMatcher.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.cryptomator.filesystem.nio.integrationtests; - -import static org.hamcrest.CoreMatchers.equalTo; - -import org.cryptomator.common.test.matcher.PropertyMatcher; -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.Folder; -import org.hamcrest.Matcher; - -class NioNodeMatcher { - - public static Matcher folderWithName(String name) { - return new PropertyMatcher<>(Folder.class, Folder::name, "name", equalTo(name)); - } - - public static Matcher fileWithName(String name) { - return new PropertyMatcher<>(File.class, File::name, "name", equalTo(name)); - } - -} diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/PathMatcher.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/PathMatcher.java deleted file mode 100644 index b9e64de32..000000000 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/integrationtests/PathMatcher.java +++ /dev/null @@ -1,145 +0,0 @@ -package org.cryptomator.filesystem.nio.integrationtests; - -import static java.util.Arrays.copyOfRange; -import static org.hamcrest.CoreMatchers.is; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Arrays; - -import org.hamcrest.Description; -import org.hamcrest.FeatureMatcher; -import org.hamcrest.Matcher; -import org.hamcrest.TypeSafeDiagnosingMatcher; - -class PathMatcher { - - public static Matcher isDirectory() { - return new FeatureMatcher(is(true), "a path for which Files.isDirectory", "Files.isDirectory") { - @Override - protected Boolean featureValueOf(Path actual) { - return Files.isDirectory(actual); - } - }; - } - - public static IsFileMatcher isFile() { - return IsFileMatcher.INSTANCE; - } - - public static Matcher doesNotExist() { - return new FeatureMatcher(is(false), "a path for which Files.exists", "Files.exists") { - @Override - protected Boolean featureValueOf(Path actual) { - return Files.exists(actual); - } - }; - } - - public static Matcher doesExist() { - return new FeatureMatcher(is(true), "a path for which Files.exists", "Files.exists") { - @Override - protected Boolean featureValueOf(Path actual) { - return Files.exists(actual); - } - }; - } - - public static class IsFileMatcher extends FeatureMatcher { - - public static final IsFileMatcher INSTANCE = new IsFileMatcher(); - - private IsFileMatcher() { - super(is(true), "a path for which Files.isRegularFile", "Files.isRegularFile"); - } - - @Override - protected Boolean featureValueOf(Path actual) { - return Files.isRegularFile(actual); - } - - public Matcher withContent(String value) { - return new IsFileWithContentMatcher(value.getBytes()); - } - - public Matcher withPartialContentAt(int offset, byte[] value) { - return new IsFileWithContentMatcher(offset, value); - } - - public Matcher withContent(byte[] value) { - return new IsFileWithContentMatcher(value); - } - - public Matcher thatIsEmpty() { - return withContent(new byte[0]); - } - - } - - public static class IsFileWithContentMatcher extends TypeSafeDiagnosingMatcher { - - private static final int NO_OFFSET = -1; - - private final byte[] expectedContent; - private final int offset; - - public IsFileWithContentMatcher(int offset, byte[] content) { - this.expectedContent = content; - this.offset = offset; - } - - public IsFileWithContentMatcher(byte[] content) { - this.expectedContent = content; - this.offset = NO_OFFSET; - } - - @Override - public void describeTo(Description description) { - description // - .appendText("a file with content ") // - .appendText(Arrays.toString(expectedContent)); - if (offset != NO_OFFSET) { - description.appendText(" starting at byte ") // - .appendValue(offset); - } - } - - @Override - protected boolean matchesSafely(Path path, Description mismatchDescription) { - if (!IsFileMatcher.INSTANCE.matches(path)) { - IsFileMatcher.INSTANCE.describeMismatch(path, mismatchDescription); - return false; - } - - byte[] actualContent = getFileContent(path); - if (offset != NO_OFFSET) { - actualContent = copyOfRange(actualContent, offset, offset + expectedContent.length); - } - - if (!Arrays.equals(actualContent, expectedContent)) { - mismatchDescription // - .appendText("a file with content ") // - .appendText(Arrays.toString(actualContent)); - if (offset != NO_OFFSET) { - mismatchDescription.appendText(" starting at byte ") // - .appendValue(offset); - } - return false; - } - - return true; - } - - private byte[] getFileContent(Path path) { - try { - return Files.readAllBytes(path); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - } - -} diff --git a/main/filesystem-nio/src/test/resources/log4j2.xml b/main/filesystem-nio/src/test/resources/log4j2.xml deleted file mode 100644 index 9b4889392..000000000 --- a/main/filesystem-nio/src/test/resources/log4j2.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/main/filesystem-stats/.gitignore b/main/filesystem-stats/.gitignore deleted file mode 100644 index b83d22266..000000000 --- a/main/filesystem-stats/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target/ diff --git a/main/filesystem-stats/pom.xml b/main/filesystem-stats/pom.xml deleted file mode 100644 index b4abb4084..000000000 --- a/main/filesystem-stats/pom.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - 4.0.0 - - org.cryptomator - main - 1.3.0-SNAPSHOT - - filesystem-stats - Cryptomator filesystem: Throughput statistics - - - - 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-stats/src/main/java/org/cryptomator/filesystem/stats/StatsFile.java b/main/filesystem-stats/src/main/java/org/cryptomator/filesystem/stats/StatsFile.java deleted file mode 100644 index 490865157..000000000 --- a/main/filesystem-stats/src/main/java/org/cryptomator/filesystem/stats/StatsFile.java +++ /dev/null @@ -1,57 +0,0 @@ -/******************************************************************************* - * 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.stats; - -import java.io.UncheckedIOException; -import java.nio.ByteBuffer; -import java.util.function.Consumer; - -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.filesystem.WritableFile; -import org.cryptomator.filesystem.delegating.DelegatingFile; -import org.cryptomator.filesystem.delegating.DelegatingReadableFile; -import org.cryptomator.filesystem.delegating.DelegatingWritableFile; - -class StatsFile extends DelegatingFile { - - private final Consumer readCounter; - private final Consumer writeCounter; - - public StatsFile(StatsFolder parent, File delegate, Consumer readCounter, Consumer writeCounter) { - super(parent, delegate); - this.readCounter = readCounter; - this.writeCounter = writeCounter; - } - - @Override - public ReadableFile openReadable() throws UncheckedIOException { - return new DelegatingReadableFile(delegate.openReadable()) { - @Override - public int read(ByteBuffer target) throws UncheckedIOException { - int num = super.read(target); - readCounter.accept((long) num); - return num; - } - }; - } - - @Override - public WritableFile openWritable() throws UncheckedIOException { - return new DelegatingWritableFile(delegate.openWritable()) { - @Override - public int write(ByteBuffer source) throws UncheckedIOException { - int num = super.write(source); - writeCounter.accept((long) num); - return num; - } - }; - } - -} diff --git a/main/filesystem-stats/src/main/java/org/cryptomator/filesystem/stats/StatsFileSystem.java b/main/filesystem-stats/src/main/java/org/cryptomator/filesystem/stats/StatsFileSystem.java deleted file mode 100644 index 9a215a370..000000000 --- a/main/filesystem-stats/src/main/java/org/cryptomator/filesystem/stats/StatsFileSystem.java +++ /dev/null @@ -1,44 +0,0 @@ -/******************************************************************************* - * 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.stats; - -import java.util.concurrent.atomic.LongAdder; - -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.delegating.DelegatingFileSystem; - -public class StatsFileSystem extends StatsFolder implements DelegatingFileSystem { - - private final LongAdder read; - private final LongAdder written; - - public StatsFileSystem(Folder root) { - this(root, new LongAdder(), new LongAdder()); - } - - private StatsFileSystem(Folder root, LongAdder read, LongAdder written) { - super(null, root, read::add, written::add); - this.read = read; - this.written = written; - } - - public long getThenResetBytesRead() { - return read.sumThenReset(); - } - - public long getThenResetBytesWritten() { - return written.sumThenReset(); - } - - @Override - public Folder getDelegate() { - return delegate; - } - -} diff --git a/main/filesystem-stats/src/main/java/org/cryptomator/filesystem/stats/StatsFolder.java b/main/filesystem-stats/src/main/java/org/cryptomator/filesystem/stats/StatsFolder.java deleted file mode 100644 index 587aeb988..000000000 --- a/main/filesystem-stats/src/main/java/org/cryptomator/filesystem/stats/StatsFolder.java +++ /dev/null @@ -1,38 +0,0 @@ -/******************************************************************************* - * 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.stats; - -import java.util.function.Consumer; - -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.delegating.DelegatingFolder; - -class StatsFolder extends DelegatingFolder { - - private final Consumer readCounter; - private final Consumer writeCounter; - - public StatsFolder(StatsFolder parent, Folder delegate, Consumer readCounter, Consumer writeCounter) { - super(parent, delegate); - this.readCounter = readCounter; - this.writeCounter = writeCounter; - } - - @Override - protected StatsFile newFile(File delegate) { - return new StatsFile(this, delegate, readCounter, writeCounter); - } - - @Override - protected StatsFolder newFolder(Folder delegate) { - return new StatsFolder(this, delegate, readCounter, writeCounter); - } - -} diff --git a/main/filesystem-stats/src/test/java/org/cryptomator/filesystem/stats/StatsFileSystemTest.java b/main/filesystem-stats/src/test/java/org/cryptomator/filesystem/stats/StatsFileSystemTest.java deleted file mode 100644 index 5728bdfb8..000000000 --- a/main/filesystem-stats/src/test/java/org/cryptomator/filesystem/stats/StatsFileSystemTest.java +++ /dev/null @@ -1,50 +0,0 @@ -/******************************************************************************* - * 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.stats; - -import java.nio.ByteBuffer; - -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.filesystem.WritableFile; -import org.cryptomator.filesystem.inmem.InMemoryFileSystem; -import org.junit.Assert; -import org.junit.Test; - -public class StatsFileSystemTest { - - @Test - public void testReadAndWriteCounters() { - FileSystem underlyingFs = new InMemoryFileSystem(); - StatsFileSystem statsFs = new StatsFileSystem(underlyingFs); - statsFs.folder("foo").create(); - File testFile = statsFs.folder("foo").file("bar"); - - Assert.assertEquals(0l, statsFs.getThenResetBytesRead()); - Assert.assertEquals(0l, statsFs.getThenResetBytesWritten()); - - try (WritableFile w = testFile.openWritable()) { - w.write(ByteBuffer.allocate(15)); - } - - Assert.assertEquals(0l, statsFs.getThenResetBytesRead()); - Assert.assertEquals(15l, statsFs.getThenResetBytesWritten()); - Assert.assertEquals(0l, statsFs.getThenResetBytesWritten()); - - try (ReadableFile r = testFile.openReadable()) { - r.read(ByteBuffer.allocate(3)); - } - - Assert.assertEquals(3l, statsFs.getThenResetBytesRead()); - Assert.assertEquals(0l, statsFs.getThenResetBytesRead()); - Assert.assertEquals(0l, statsFs.getThenResetBytesWritten()); - } - -} diff --git a/main/filesystem-stats/src/test/java/org/cryptomator/filesystem/stats/StatsFileTest.java b/main/filesystem-stats/src/test/java/org/cryptomator/filesystem/stats/StatsFileTest.java deleted file mode 100644 index 564ad90e7..000000000 --- a/main/filesystem-stats/src/test/java/org/cryptomator/filesystem/stats/StatsFileTest.java +++ /dev/null @@ -1,58 +0,0 @@ -/******************************************************************************* - * 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.stats; - -import java.nio.ByteBuffer; -import java.util.function.Consumer; - -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.filesystem.WritableFile; -import org.junit.Test; -import org.mockito.Mockito; - -public class StatsFileTest { - - @Test - public void testStatsDuringRead() { - ReadableFile readable = Mockito.mock(ReadableFile.class); - File file = Mockito.mock(File.class); - Mockito.when(file.openReadable()).thenReturn(readable); - - @SuppressWarnings("unchecked") - Consumer readCounter = Mockito.mock(Consumer.class); - File statsFile = new StatsFile(null, file, readCounter, null); - - Mockito.when(readable.read(Mockito.any())).thenReturn(123); - try (ReadableFile r = statsFile.openReadable()) { - r.read(ByteBuffer.allocate(0)); - } - - Mockito.verify(readCounter).accept(123l); - } - - @Test - public void testStatsDuringWrite() { - WritableFile writable = Mockito.mock(WritableFile.class); - File file = Mockito.mock(File.class); - Mockito.when(file.openWritable()).thenReturn(writable); - - @SuppressWarnings("unchecked") - Consumer writeCounter = Mockito.mock(Consumer.class); - File statsFile = new StatsFile(null, file, null, writeCounter); - - Mockito.when(writable.write(Mockito.any())).thenReturn(123); - try (WritableFile w = statsFile.openWritable()) { - w.write(ByteBuffer.allocate(0)); - } - - Mockito.verify(writeCounter).accept(123l); - } - -} diff --git a/main/frontend-api/.gitignore b/main/frontend-api/.gitignore deleted file mode 100644 index b83d22266..000000000 --- a/main/frontend-api/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target/ diff --git a/main/frontend-api/pom.xml b/main/frontend-api/pom.xml deleted file mode 100644 index abf1de672..000000000 --- a/main/frontend-api/pom.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - 4.0.0 - - org.cryptomator - main - 1.3.0-SNAPSHOT - - frontend-api - Cryptomator frontend: API - API for filesystem frontends - - - - org.cryptomator - filesystem-api - - - \ No newline at end of file diff --git a/main/frontend-api/src/main/java/org/cryptomator/frontend/CommandFailedException.java b/main/frontend-api/src/main/java/org/cryptomator/frontend/CommandFailedException.java deleted file mode 100644 index 956f901f5..000000000 --- a/main/frontend-api/src/main/java/org/cryptomator/frontend/CommandFailedException.java +++ /dev/null @@ -1,24 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014, 2016 Sebastian Stenzel - * 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 - * Markus Kreusch - Refactored WebDavMounter to use strategy pattern - ******************************************************************************/ -package org.cryptomator.frontend; - -public class CommandFailedException extends Exception { - - private static final long serialVersionUID = 5784853630182321479L; - - public CommandFailedException(String message) { - super(message); - } - - public CommandFailedException(Throwable cause) { - super(cause); - } - -} \ No newline at end of file diff --git a/main/frontend-api/src/main/java/org/cryptomator/frontend/Frontend.java b/main/frontend-api/src/main/java/org/cryptomator/frontend/Frontend.java deleted file mode 100644 index 78978bf7b..000000000 --- a/main/frontend-api/src/main/java/org/cryptomator/frontend/Frontend.java +++ /dev/null @@ -1,37 +0,0 @@ -/******************************************************************************* - * 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.frontend; - -import java.util.Map; -import java.util.Optional; - -public interface Frontend extends AutoCloseable { - - public enum MountParam { - MOUNT_NAME, HOSTNAME, WIN_DRIVE_LETTER, - - /** - * "dav" or "webdav" - */ - PREFERRED_GVFS_SCHEME - } - - void mount(Map> map) throws CommandFailedException; - - /** - * Unmounts the file system and stops any file system handler threads. - */ - void close() throws Exception; - - void reveal() throws CommandFailedException; - - // For now let's assume every single frontend knows what a WebDAV url is ;-) - String getWebDavUrl(); - -} diff --git a/main/frontend-api/src/main/java/org/cryptomator/frontend/FrontendCreationFailedException.java b/main/frontend-api/src/main/java/org/cryptomator/frontend/FrontendCreationFailedException.java deleted file mode 100644 index 5bcb1442b..000000000 --- a/main/frontend-api/src/main/java/org/cryptomator/frontend/FrontendCreationFailedException.java +++ /dev/null @@ -1,17 +0,0 @@ -/******************************************************************************* - * 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.frontend; - -public class FrontendCreationFailedException extends Exception { - - public FrontendCreationFailedException(Throwable cause) { - super(cause); - } - -} diff --git a/main/frontend-api/src/main/java/org/cryptomator/frontend/FrontendFactory.java b/main/frontend-api/src/main/java/org/cryptomator/frontend/FrontendFactory.java deleted file mode 100644 index 37fb053cd..000000000 --- a/main/frontend-api/src/main/java/org/cryptomator/frontend/FrontendFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -/******************************************************************************* - * 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.frontend; - -import org.cryptomator.filesystem.Folder; - -public interface FrontendFactory { - - /** - * Provides a new frontend to access the given folder. - * - * @param root Root resource accessible through this frontend. - * @param id unique id of the frontend, i.e. used to generate a unique uri - * @param name Name of the frontend, i.e. used to generate a readable/recognizable name of a common virtual drive - * @return A new frontend - * @throws FrontendCreationFailedException If creation was not possible. - */ - Frontend create(Folder root, FrontendId id, String name) throws FrontendCreationFailedException; - -} diff --git a/main/frontend-api/src/main/java/org/cryptomator/frontend/FrontendId.java b/main/frontend-api/src/main/java/org/cryptomator/frontend/FrontendId.java deleted file mode 100644 index d5608bdac..000000000 --- a/main/frontend-api/src/main/java/org/cryptomator/frontend/FrontendId.java +++ /dev/null @@ -1,85 +0,0 @@ -package org.cryptomator.frontend; - -import static java.util.UUID.randomUUID; - -import java.io.Serializable; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.UUID; - -public class FrontendId implements Serializable { - - public static final String FRONTEND_ID_PATTERN = "[a-zA-Z0-9_-]{12}"; - - public static FrontendId generate() { - return new FrontendId(); - } - - public static FrontendId from(String value) { - return new FrontendId(value); - } - - private final String value; - - private FrontendId() { - this(generateId()); - } - - private FrontendId(String value) { - if (!value.matches(FRONTEND_ID_PATTERN)) { - throw new IllegalArgumentException("Invalid frontend id " + value); - } - this.value = value; - } - - private static String generateId() { - return asBase64String(nineBytesFrom(randomUUID())); - } - - private static String asBase64String(ByteBuffer bytes) { - ByteBuffer base64Buffer = Base64.getUrlEncoder().encode(bytes); - return new String(asByteArray(base64Buffer), StandardCharsets.US_ASCII); - } - - private static ByteBuffer nineBytesFrom(UUID uuid) { - ByteBuffer uuidBuffer = ByteBuffer.allocate(9); - uuidBuffer.putLong(uuid.getMostSignificantBits()); - uuidBuffer.put((byte) (uuid.getLeastSignificantBits() & 0xFF)); - uuidBuffer.flip(); - return uuidBuffer; - } - - private static byte[] asByteArray(ByteBuffer buffer) { - if (buffer.hasArray()) { - return buffer.array(); - } else { - byte[] bytes = new byte[buffer.remaining()]; - buffer.get(bytes); - return bytes; - } - } - - @Override - public boolean equals(Object obj) { - if (obj == null || obj.getClass() != getClass()) { - return false; - } - return obj == this || internalEquals((FrontendId) obj); - } - - private boolean internalEquals(FrontendId obj) { - return value.equals(obj.value); - } - - @Override - public int hashCode() { - return value.hashCode(); - } - - @Override - public String toString() { - return value; - } - -} diff --git a/main/frontend-api/src/main/java/org/cryptomator/frontend/package-info.java b/main/frontend-api/src/main/java/org/cryptomator/frontend/package-info.java deleted file mode 100644 index adb129c5c..000000000 --- a/main/frontend-api/src/main/java/org/cryptomator/frontend/package-info.java +++ /dev/null @@ -1,12 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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 - *******************************************************************************/ -/** - * Provides frontends for {@link org.cryptomator.filesystem.FileSystem FileSystems}. - */ -package org.cryptomator.frontend; \ No newline at end of file diff --git a/main/frontend-webdav/pom.xml b/main/frontend-webdav/pom.xml deleted file mode 100644 index f92a99b8d..000000000 --- a/main/frontend-webdav/pom.xml +++ /dev/null @@ -1,134 +0,0 @@ - - - - 4.0.0 - - org.cryptomator - main - 1.3.0-SNAPSHOT - - frontend-webdav - Cryptomator frontend: WebDAV frontend - Provides access via WebDAV to filesystems - - - 2.11.3 - 9.3.3.v20150827 - - - - - org.cryptomator - filesystem-api - - - org.cryptomator - commons - - - org.cryptomator - frontend-api - - - - - javax.servlet - javax.servlet-api - 3.1.0 - - - org.apache.jackrabbit - jackrabbit-webdav - ${jackrabbit.version} - - - - - org.eclipse.jetty - jetty-server - ${jetty.version} - - - org.eclipse.jetty - jetty-webapp - ${jetty.version} - - - - - com.google.guava - guava - - - - - org.apache.commons - commons-lang3 - - - commons-io - commons-io - - - - - com.google.dagger - dagger - - - com.google.dagger - dagger-compiler - provided - - - - - org.cryptomator - commons-test - - - org.cryptomator - filesystem-inmemory - - - org.cryptomator - filesystem-crypto-integration-tests - - - org.cryptomator - filesystem-nameshortening - test - - - org.cryptomator - filesystem-crypto - test - - - commons-httpclient - commons-httpclient - test - - - org.cryptomator - filesystem-nio - test - - - - - - - org.jacoco - jacoco-maven-plugin - - - - diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FileLocator.java b/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FileLocator.java deleted file mode 100644 index 01846b47e..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FileLocator.java +++ /dev/null @@ -1,55 +0,0 @@ -/******************************************************************************* - * 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.jackrabbit; - -import java.util.concurrent.atomic.AtomicReference; - -import org.apache.jackrabbit.webdav.DavLocatorFactory; -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.delegating.DelegatingFile; - -public class FileLocator extends DelegatingFileimplements InternalFileSystemResourceLocator { - - private final DavLocatorFactory factory; - private final String prefix; - private final AtomicReference resourcePath = new AtomicReference<>(); - - public FileLocator(DavLocatorFactory factory, String prefix, FolderLocator parent, File delegate) { - super(parent, delegate); - this.factory = factory; - this.prefix = prefix; - } - - @Override - public String getPrefix() { - return prefix; - } - - @Override - public boolean isRootLocation() { - return false; - } - - @Override - public DavLocatorFactory getFactory() { - return factory; - } - - @Override - public AtomicReference getResourcePathRef() { - return resourcePath; - } - - @Override - public String computeResourcePath() { - assert parent().isPresent(); - return parent().get().getResourcePath() + name(); - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FileSystemLocator.java b/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FileSystemLocator.java deleted file mode 100644 index a407889fc..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FileSystemLocator.java +++ /dev/null @@ -1,36 +0,0 @@ -/******************************************************************************* - * 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.jackrabbit; - -import org.apache.jackrabbit.webdav.DavLocatorFactory; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.delegating.DelegatingFileSystem; - -class FileSystemLocator extends FolderLocator implements DelegatingFileSystem { - - public FileSystemLocator(DavLocatorFactory factory, String prefix, Folder delegate) { - super(factory, prefix, null, delegate); - } - - @Override - public boolean isRootLocation() { - return true; - } - - @Override - public String getResourcePath() { - return "/"; - } - - @Override - public Folder getDelegate() { - return delegate; - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FileSystemResourceLocator.java b/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FileSystemResourceLocator.java deleted file mode 100644 index 0ee442644..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FileSystemResourceLocator.java +++ /dev/null @@ -1,58 +0,0 @@ -/******************************************************************************* - * 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.jackrabbit; - -import java.util.Optional; - -import org.apache.jackrabbit.webdav.DavResourceLocator; -import org.apache.jackrabbit.webdav.util.EncodeUtil; -import org.cryptomator.filesystem.Node; - -public interface FileSystemResourceLocator extends DavResourceLocator, Node { - - @Override - Optional parent(); - - @Override - default String getWorkspacePath() { - return null; - } - - @Override - default String getWorkspaceName() { - return null; - } - - @Override - default boolean isSameWorkspace(DavResourceLocator locator) { - return false; - } - - @Override - default boolean isSameWorkspace(String workspaceName) { - return false; - } - - default String getHref() { - final boolean isCollection = getResourcePath().endsWith("/"); - return getHref(isCollection); - } - - @Override - default String getHref(boolean isCollection) { - final String encodedResourcePath = EncodeUtil.escapePath(getResourcePath()); - return getPrefix() + encodedResourcePath; - } - - @Override - default String getRepositoryPath() { - return getResourcePath(); - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FileSystemResourceLocatorFactory.java b/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FileSystemResourceLocatorFactory.java deleted file mode 100644 index 6d91443a3..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FileSystemResourceLocatorFactory.java +++ /dev/null @@ -1,56 +0,0 @@ -/******************************************************************************* - * 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.jackrabbit; - -import java.net.URI; - -import org.apache.commons.lang3.StringUtils; -import org.apache.jackrabbit.webdav.DavLocatorFactory; -import org.apache.jackrabbit.webdav.util.EncodeUtil; -import org.cryptomator.filesystem.Folder; - -public class FileSystemResourceLocatorFactory implements DavLocatorFactory { - - private final FileSystemLocator fs; - - public FileSystemResourceLocatorFactory(URI contextRootUri, Folder root) { - String pathPrefix = StringUtils.removeEnd(contextRootUri.toString(), "/"); - this.fs = new FileSystemLocator(this, pathPrefix, root); - } - - @Override - public FileSystemResourceLocator createResourceLocator(String prefix, String href) { - final String fullPrefix = StringUtils.removeEnd(prefix, "/"); - final String remainingHref = StringUtils.removeStart(href, fullPrefix); - final String unencodedRemaingingHref = EncodeUtil.unescape(remainingHref); - return createResourceLocator(unencodedRemaingingHref); - } - - @Override - public FileSystemResourceLocator createResourceLocator(String prefix, String workspacePath, String resourcePath) { - return createResourceLocator(resourcePath); - } - - @Override - public FileSystemResourceLocator createResourceLocator(String prefix, String workspacePath, String path, boolean isResourcePath) { - return createResourceLocator(path); - } - - private FileSystemResourceLocator createResourceLocator(String path) { - if (StringUtils.isEmpty(path) || "/".equals(path)) { - return fs; - } else if (path.endsWith("/")) { - return fs.resolveFolder(path); - } else { - return fs.resolveFile(path); - } - - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FolderLocator.java b/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FolderLocator.java deleted file mode 100644 index e1532d7f5..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FolderLocator.java +++ /dev/null @@ -1,77 +0,0 @@ -/******************************************************************************* - * 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.jackrabbit; - -import java.io.UncheckedIOException; -import java.util.concurrent.atomic.AtomicReference; - -import org.apache.jackrabbit.webdav.DavLocatorFactory; -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.delegating.DelegatingFolder; - -public class FolderLocator extends DelegatingFolderimplements InternalFileSystemResourceLocator { - - private final DavLocatorFactory factory; - private final String prefix; - private final AtomicReference resourcePath = new AtomicReference<>(); - - public FolderLocator(DavLocatorFactory factory, String prefix, FolderLocator parent, Folder delegate) { - super(parent, delegate); - this.factory = factory; - this.prefix = prefix; - } - - @Override - protected FileLocator newFile(File delegate) { - return new FileLocator(factory, prefix, this, delegate); - } - - @Override - public FileLocator resolveFile(String relativePath) throws UncheckedIOException { - return (FileLocator) super.resolveFile(relativePath); - } - - @Override - protected FolderLocator newFolder(Folder delegate) { - return new FolderLocator(factory, prefix, this, delegate); - } - - @Override - public FolderLocator resolveFolder(String relativePath) throws UncheckedIOException { - return (FolderLocator) super.resolveFolder(relativePath); - } - - @Override - public String getPrefix() { - return prefix; - } - - @Override - public boolean isRootLocation() { - return false; - } - - @Override - public DavLocatorFactory getFactory() { - return factory; - } - - @Override - public AtomicReference getResourcePathRef() { - return resourcePath; - } - - @Override - public String computeResourcePath() { - assert parent().isPresent(); - return parent().get().getResourcePath() + name() + "/"; - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/InternalFileSystemResourceLocator.java b/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/InternalFileSystemResourceLocator.java deleted file mode 100644 index bcf67bbd0..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/InternalFileSystemResourceLocator.java +++ /dev/null @@ -1,29 +0,0 @@ -/******************************************************************************* - * 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.jackrabbit; - -import java.util.concurrent.atomic.AtomicReference; - -import org.cryptomator.common.LazyInitializer; - -/** - * Adds package-private API to {@link FileSystemResourceLocator}. - */ -interface InternalFileSystemResourceLocator extends FileSystemResourceLocator { - - @Override - default String getResourcePath() { - return LazyInitializer.initializeLazily(getResourcePathRef(), this::computeResourcePath); - } - - AtomicReference getResourcePathRef(); - - String computeResourcePath(); - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/ContextPathBuilder.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/ContextPathBuilder.java deleted file mode 100644 index 6e9c6a7e8..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/ContextPathBuilder.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.cryptomator.frontend.webdav; - -import static java.lang.String.format; -import static org.cryptomator.frontend.FrontendId.FRONTEND_ID_PATTERN; - -import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.cryptomator.frontend.FrontendId; - -class ContextPaths { - - private static final Pattern SERVLET_PATH_WITH_FRONTEND_ID_PATTERN = Pattern.compile("^/(" + FRONTEND_ID_PATTERN + ")(/.*)?$"); - private static final int FRONTEND_ID_GROUP = 1; - - public static String from(FrontendId id, String name) { - return format("/%s/%s", id, name); - } - - public static String removeFrontendId(String path) { - return path.replaceAll("/" + FRONTEND_ID_PATTERN + "/", "/[...]/"); - } - - public static Optional extractFrontendId(String path) { - Matcher matcher = SERVLET_PATH_WITH_FRONTEND_ID_PATTERN.matcher(path); - if (matcher.matches()) { - return Optional.of(FrontendId.from(matcher.group(FRONTEND_ID_GROUP))); - } else { - return Optional.empty(); - } - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/DefaultServlet.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/DefaultServlet.java deleted file mode 100644 index dfd74a572..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/DefaultServlet.java +++ /dev/null @@ -1,61 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav; - -import java.io.IOException; -import java.util.EnumSet; - -import javax.inject.Inject; -import javax.inject.Singleton; -import javax.servlet.DispatcherType; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.cryptomator.frontend.webdav.filters.LoopbackFilter; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; - -@Singleton -class DefaultServlet extends HttpServlet { - - private static final String ROOT_PATH = "/"; - private static final String WILDCARD = "/*"; - - private final Tarpit tarpit; - - @Inject - public DefaultServlet(Tarpit tarpit) { - this.tarpit = tarpit; - } - - @Override - protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - tarpit.handle(req); - super.service(req, resp); - } - - @Override - protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - resp.addHeader("DAV", "1, 2"); - resp.addHeader("MS-Author-Via", "DAV"); - resp.addHeader("Allow", "OPTIONS, GET, HEAD"); - resp.setStatus(HttpServletResponse.SC_NO_CONTENT); - } - - public ServletContextHandler createServletContextHandler() { - final ServletContextHandler servletContext = new ServletContextHandler(null, ROOT_PATH, ServletContextHandler.NO_SESSIONS); - final ServletHolder servletHolder = new ServletHolder(ROOT_PATH, this); - servletContext.addServlet(servletHolder, ROOT_PATH); - servletContext.addFilter(LoopbackFilter.class, WILDCARD, EnumSet.of(DispatcherType.REQUEST)); - return servletContext; - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/FontendIdHidingServletContextHandler.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/FontendIdHidingServletContextHandler.java deleted file mode 100644 index 9b983d9ac..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/FontendIdHidingServletContextHandler.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.cryptomator.frontend.webdav; - -import org.eclipse.jetty.server.HandlerContainer; -import org.eclipse.jetty.servlet.ServletContextHandler; - -class FontendIdHidingServletContextHandler extends ServletContextHandler { - - public FontendIdHidingServletContextHandler(HandlerContainer parent, String contextPath, int options) { - super(parent, contextPath, options); - } - - @Override - public String toString() { - return ContextPaths.removeFrontendId(super.toString()); - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/Tarpit.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/Tarpit.java deleted file mode 100644 index 0c40d7e0f..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/Tarpit.java +++ /dev/null @@ -1,71 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Markus Kreusch - * This file is licensed under the terms of the MIT license. - * See the LICENSE.txt file for more info. - *******************************************************************************/ -package org.cryptomator.frontend.webdav; - -import static java.lang.Math.max; -import static java.lang.System.currentTimeMillis; -import static java.util.Collections.synchronizedSet; - -import java.io.Serializable; -import java.util.Collection; -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; - -import javax.inject.Inject; -import javax.inject.Singleton; -import javax.servlet.http.HttpServletRequest; - -import org.cryptomator.frontend.FrontendId; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@Singleton -class Tarpit implements Serializable { - - private static final Logger LOG = LoggerFactory.getLogger(Tarpit.class); - private static final long DELAY_MS = 10000; - - private final Set validFrontendIds = synchronizedSet(new HashSet<>()); - - @Inject - public Tarpit() { - } - - public void setValidFrontendIds(Collection validFrontendIds) { - this.validFrontendIds.retainAll(validFrontendIds); - this.validFrontendIds.addAll(validFrontendIds); - } - - public void handle(HttpServletRequest req) { - if (isRequestWithInvalidVaultId(req)) { - delayExecutionUninterruptibly(); - LOG.debug("Delayed request to " + req.getRequestURI() + " by " + DELAY_MS + "ms"); - } - } - - private boolean isRequestWithInvalidVaultId(HttpServletRequest req) { - Optional frontendId = ContextPaths.extractFrontendId(req.getServletPath()); - return frontendId.isPresent() && !isValid(frontendId.get()); - } - - private void delayExecutionUninterruptibly() { - long expected = currentTimeMillis() + DELAY_MS; - long sleepTime = DELAY_MS; - while (expected > currentTimeMillis()) { - try { - Thread.sleep(sleepTime); - } catch (InterruptedException e) { - sleepTime = max(0, currentTimeMillis() - expected + 10); - } - } - } - - private boolean isValid(FrontendId frontendId) { - return validFrontendIds.contains(frontendId); - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavFrontend.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavFrontend.java deleted file mode 100644 index 0bfd9515d..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavFrontend.java +++ /dev/null @@ -1,71 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav; - -import java.net.URI; -import java.util.Map; -import java.util.Optional; - -import org.cryptomator.frontend.CommandFailedException; -import org.cryptomator.frontend.Frontend; -import org.cryptomator.frontend.FrontendCreationFailedException; -import org.cryptomator.frontend.webdav.mount.WebDavMount; -import org.cryptomator.frontend.webdav.mount.WebDavMounterProvider; -import org.eclipse.jetty.servlet.ServletContextHandler; - -class WebDavFrontend implements Frontend { - - private final WebDavMounterProvider webdavMounterProvider; - private final ServletContextHandler handler; - private final URI uri; - - private WebDavMount mount; - - public WebDavFrontend(WebDavMounterProvider webdavMounterProvider, ServletContextHandler handler, URI uri) throws FrontendCreationFailedException { - this.webdavMounterProvider = webdavMounterProvider; - this.handler = handler; - this.uri = uri; - try { - handler.start(); - } catch (Exception e) { - throw new FrontendCreationFailedException(e); - } - } - - @Override - public void close() throws Exception { - unmount(); - handler.stop(); - } - - @Override - public void mount(Map> mountParams) throws CommandFailedException { - mount = webdavMounterProvider.chooseMounter(mountParams).mount(uri, mountParams); - } - - private void unmount() throws CommandFailedException { - if (mount != null) { - mount.unmount(); - mount = null; - } - } - - @Override - public void reveal() throws CommandFailedException { - if (mount != null) { - mount.reveal(); - } - } - - @Override - public String getWebDavUrl() { - return uri.toString(); - } - -} \ No newline at end of file diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavModule.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavModule.java deleted file mode 100644 index 7603d867e..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavModule.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.cryptomator.frontend.webdav; - -import org.cryptomator.common.CommonsModule; -import org.cryptomator.frontend.webdav.mount.WebDavMounterModule; - -import dagger.Module; - -@Module(includes = {CommonsModule.class, WebDavMounterModule.class}) -public class WebDavModule { - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavServer.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavServer.java deleted file mode 100644 index 81ed65087..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavServer.java +++ /dev/null @@ -1,127 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Collection; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import org.cryptomator.filesystem.Folder; -import org.cryptomator.frontend.Frontend; -import org.cryptomator.frontend.FrontendCreationFailedException; -import org.cryptomator.frontend.FrontendFactory; -import org.cryptomator.frontend.FrontendId; -import org.cryptomator.frontend.webdav.mount.WebDavMounterProvider; -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.server.handler.ContextHandlerCollection; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.util.thread.QueuedThreadPool; -import org.eclipse.jetty.util.thread.ThreadPool; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@Singleton -public class WebDavServer implements FrontendFactory { - - private static final Logger LOG = LoggerFactory.getLogger(WebDavServer.class); - private static final int MAX_PENDING_REQUESTS = 200; - private static final int MAX_THREADS = 200; - private static final int MIN_THREADS = 4; - private static final int THREAD_IDLE_SECONDS = 20; - - private final Server server; - private final ServerConnector localConnector; - private final ContextHandlerCollection servletCollection; - private final WebDavServletContextFactory servletContextFactory; - private final WebDavMounterProvider webdavMounterProvider; - private final Tarpit tarpit; - - @Inject - WebDavServer(WebDavServletContextFactory servletContextFactory, WebDavMounterProvider webdavMounterProvider, DefaultServlet defaultServlet, Tarpit tarpit) { - final BlockingQueue queue = new LinkedBlockingQueue<>(MAX_PENDING_REQUESTS); - final ThreadPool tp = new QueuedThreadPool(MAX_THREADS, MIN_THREADS, THREAD_IDLE_SECONDS, queue); - this.server = new Server(tp); - this.localConnector = new ServerConnector(server); - this.servletCollection = new ContextHandlerCollection(); - this.servletContextFactory = servletContextFactory; - this.webdavMounterProvider = webdavMounterProvider; - this.tarpit = tarpit; - - servletCollection.addHandler(defaultServlet.createServletContextHandler()); - server.setConnectors(new Connector[] {localConnector}); - server.setHandler(servletCollection); - } - - public void setPort(int port) { - if (server.isStopped()) { - localConnector.setPort(port); - } else { - throw new IllegalStateException("Cannot change port of running server."); - } - } - - public int getPort() { - return localConnector.getLocalPort(); - } - - public synchronized void start() { - try { - server.start(); - LOG.info("Cryptomator is running on port {}", getPort()); - } catch (Exception ex) { - throw new RuntimeException("Server couldn't be started", ex); - } - } - - public boolean isRunning() { - return server.isRunning(); - } - - public synchronized void stop() { - try { - server.stop(); - } catch (Exception ex) { - LOG.error("Server couldn't be stopped", ex); - } - } - - // visible for testing - ServletContextHandler addServlet(Folder root, URI contextRoot) { - ServletContextHandler handler = servletContextFactory.create(contextRoot, root); - servletCollection.addHandler(handler); - servletCollection.mapContexts(); - return handler; - } - - @Override - public Frontend create(Folder root, FrontendId id, String name) throws FrontendCreationFailedException { - String contextPath = ContextPaths.from(id, name); - final URI uri; - try { - uri = new URI("http", null, "localhost", getPort(), contextPath, null, null); - } catch (URISyntaxException e) { - throw new IllegalStateException(e); - } - final ServletContextHandler handler = addServlet(root, uri); - LOG.info("Servlet available under " + ContextPaths.removeFrontendId(uri.toString())); - return new WebDavFrontend(webdavMounterProvider, handler, uri); - } - - public void setValidFrontendIds(Collection validFrontendIds) { - tarpit.setValidFrontendIds(validFrontendIds); - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavServletContextFactory.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavServletContextFactory.java deleted file mode 100644 index ca6fcbebf..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavServletContextFactory.java +++ /dev/null @@ -1,81 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav; - -import java.net.URI; -import java.util.EnumSet; - -import javax.inject.Inject; -import javax.inject.Singleton; -import javax.servlet.DispatcherType; - -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.SystemUtils; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.frontend.webdav.filters.AcceptRangeFilter; -import org.cryptomator.frontend.webdav.filters.LoopbackFilter; -import org.cryptomator.frontend.webdav.filters.MacChunkedPutCompatibilityFilter; -import org.cryptomator.frontend.webdav.filters.MkcolComplianceFilter; -import org.cryptomator.frontend.webdav.filters.PostRequestBlockingFilter; -import org.cryptomator.frontend.webdav.filters.UriNormalizationFilter; -import org.cryptomator.frontend.webdav.filters.UriNormalizationFilter.ResourceTypeChecker; -import org.cryptomator.frontend.webdav.filters.UriNormalizationFilter.ResourceTypeChecker.ResourceType; -import org.cryptomator.frontend.webdav.jackrabbitservlet.WebDavServlet; -import org.eclipse.jetty.servlet.FilterHolder; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; - -@Singleton -class WebDavServletContextFactory { - - private static final String WILDCARD = "/*"; - - @Inject - public WebDavServletContextFactory() { - } - - /** - * Creates a new Jetty ServletContextHandler, that can be be added to a servletCollection as follows: - * - *
-	 * ServletContextHandler context = factory.create(...);
-	 * servletCollection.addHandler(context);
-	 * servletCollection.mapContexts();
-	 * 
- * - * @param contextRoot The URI of the context root. Its path will be used as the servlet's context path. - * @param root The location within a filesystem that shall be served via WebDAV. - * @return A new Jetty servlet context handler. - */ - public ServletContextHandler create(URI contextRoot, Folder root) { - final ResourceTypeChecker resourceTypeChecker = (path) -> { - if (root.resolveFolder(path).exists()) { - return ResourceType.FOLDER; - } else if (root.resolveFile(path).exists()) { - return ResourceType.FILE; - } else { - return ResourceType.UNKNOWN; - } - }; - final String contextPath = StringUtils.removeEnd(contextRoot.getPath(), "/"); - final ServletContextHandler servletContext = new FontendIdHidingServletContextHandler(null, contextPath, ServletContextHandler.SESSIONS); - final ServletHolder servletHolder = new ServletHolder(contextPath, new WebDavServlet(contextRoot, root)); - servletContext.addServlet(servletHolder, WILDCARD); - servletContext.addFilter(LoopbackFilter.class, WILDCARD, EnumSet.of(DispatcherType.REQUEST)); - servletContext.addFilter(PostRequestBlockingFilter.class, WILDCARD, EnumSet.of(DispatcherType.REQUEST)); - servletContext.addFilter(MkcolComplianceFilter.class, WILDCARD, EnumSet.of(DispatcherType.REQUEST)); - servletContext.addFilter(AcceptRangeFilter.class, WILDCARD, EnumSet.of(DispatcherType.REQUEST)); - servletContext.addFilter(new FilterHolder(new UriNormalizationFilter(resourceTypeChecker)), WILDCARD, EnumSet.of(DispatcherType.REQUEST)); - if (SystemUtils.IS_OS_MAC_OSX) { - servletContext.addFilter(MacChunkedPutCompatibilityFilter.class, WILDCARD, EnumSet.of(DispatcherType.REQUEST)); - } - return servletContext; - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/AcceptRangeFilter.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/AcceptRangeFilter.java deleted file mode 100644 index 4c200dd79..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/AcceptRangeFilter.java +++ /dev/null @@ -1,46 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav.filters; - -import java.io.IOException; - -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * Adds an Accept-Range: bytes header to all GET requests. - */ -public class AcceptRangeFilter implements HttpFilter { - - private static final String METHOD_GET = "GET"; - private static final String HEADER_ACCEPT_RANGES = "Accept-Ranges"; - private static final String HEADER_ACCEPT_RANGE_VALUE = "bytes"; - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - // no-op - } - - @Override - public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { - if (METHOD_GET.equalsIgnoreCase(request.getMethod())) { - response.addHeader(HEADER_ACCEPT_RANGES, HEADER_ACCEPT_RANGE_VALUE); - } - chain.doFilter(request, response); - } - - @Override - public void destroy() { - // no-op - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/HttpFilter.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/HttpFilter.java deleted file mode 100644 index 9c35a669c..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/HttpFilter.java +++ /dev/null @@ -1,34 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav.filters; - -import java.io.IOException; - -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -interface HttpFilter extends Filter { - - @Override - default void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) { - doFilterHttp((HttpServletRequest) request, (HttpServletResponse) response, chain); - } else { - chain.doFilter(request, response); - } - } - - void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException; - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/LoopbackFilter.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/LoopbackFilter.java deleted file mode 100644 index 3f4235938..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/LoopbackFilter.java +++ /dev/null @@ -1,45 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav.filters; - -import java.io.IOException; -import java.net.InetAddress; - -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * Blocks all requests from external hosts. - */ -public class LoopbackFilter implements HttpFilter { - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - // no-op - } - - @Override - public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) - throws IOException, ServletException { - if (InetAddress.getByName(request.getRemoteAddr()).isLoopbackAddress()) { - chain.doFilter(request, response); - } else { - response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "Can only access drive from localhost."); - } - } - - @Override - public void destroy() { - // no-op - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/MacChunkedPutCompatibilityFilter.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/MacChunkedPutCompatibilityFilter.java deleted file mode 100644 index 144502683..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/MacChunkedPutCompatibilityFilter.java +++ /dev/null @@ -1,154 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav.filters; - -import java.io.IOException; - -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ReadListener; -import javax.servlet.ServletException; -import javax.servlet.ServletInputStream; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletRequestWrapper; -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.io.input.BoundedInputStream; - -/** - * If a PUT request with chunked transfer encoding and a X-Expected-Entity-Length header field is sent, - * the input stream will return EOF after the number of bytes stated in this header has been read. - * - * This filter ensures compatibility of the Mac OS X WebDAV client, as Macs don't terminate chunked transfers normally (by sending a 0-byte-chunk). - */ -public class MacChunkedPutCompatibilityFilter implements HttpFilter { - - private static final String METHOD_PUT = "PUT"; - private static final String HEADER_TRANSFER_ENCODING = "Transfer-Encoding"; - private static final String HEADER_X_EXPECTED_ENTITIY_LENGTH = "X-Expected-Entity-Length"; - private static final String HEADER_TRANSFER_ENCODING_CHUNKED = "chunked"; - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - // no-op - } - - @Override - public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { - final String expectedEntitiyLengthHeader = request.getHeader(HEADER_X_EXPECTED_ENTITIY_LENGTH); - if (METHOD_PUT.equalsIgnoreCase(request.getMethod()) // - && HEADER_TRANSFER_ENCODING_CHUNKED.equalsIgnoreCase(request.getHeader(HEADER_TRANSFER_ENCODING)) // - && expectedEntitiyLengthHeader != null) { - long expectedEntitiyLength; - try { - expectedEntitiyLength = Long.valueOf(expectedEntitiyLengthHeader); - } catch (NumberFormatException e) { - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid X-Expected-Entity-Length"); - return; - } - chain.doFilter(new PutRequestWithBoundedInputStream(request, expectedEntitiyLength), response); - } else { - chain.doFilter(request, response); - } - } - - @Override - public void destroy() { - // no-op - } - - private static class PutRequestWithBoundedInputStream extends HttpServletRequestWrapper { - - private final long inputStreamLimit; - - public PutRequestWithBoundedInputStream(HttpServletRequest request, long inputStreamLimit) { - super(request); - this.inputStreamLimit = inputStreamLimit; - } - - @Override - public ServletInputStream getInputStream() throws IOException { - return new BoundedServletInputStream(super.getInputStream(), inputStreamLimit); - } - - } - - /** - * Like {@link BoundedInputStream}, but as a ServletInputStream. - */ - private static class BoundedServletInputStream extends ServletInputStream { - - private final BoundedInputStream boundedIn; - private final ServletInputStream servletIn; - private boolean reachedEof = false; - private ReadListener readListener; - - public BoundedServletInputStream(ServletInputStream delegate, long limit) { - this.boundedIn = new BoundedInputStream(delegate, limit); - this.servletIn = delegate; - } - - private void reachedEof() throws IOException { - reachedEof = true; - if (readListener != null) { - readListener.onAllDataRead(); - } - } - - /* BoundedInputStream */ - - @Override - public long skip(long n) throws IOException { - return boundedIn.skip(n); - } - - @Override - public int available() throws IOException { - return boundedIn.available(); - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - int read = boundedIn.read(b, off, len); - if (read == -1) { - reachedEof(); - } - return read; - } - - @Override - public int read() throws IOException { - int aByte = boundedIn.read(); - if (aByte == -1) { - reachedEof(); - } - return aByte; - } - - /* ServletInputStream */ - - @Override - public boolean isFinished() { - return reachedEof || servletIn.isFinished(); - } - - @Override - public boolean isReady() { - return !reachedEof && servletIn.isReady(); - } - - @Override - public void setReadListener(ReadListener readListener) { - servletIn.setReadListener(readListener); - this.readListener = readListener; - } - - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/MkcolComplianceFilter.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/MkcolComplianceFilter.java deleted file mode 100644 index 2f0116041..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/MkcolComplianceFilter.java +++ /dev/null @@ -1,51 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav.filters; - -import java.io.IOException; - -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * Responds with status code 415, if an attempt is made to create a collection with a body. - * - * See https://tools.ietf.org/html/rfc2518#section-8.3.1: - * "If the server receives a MKCOL request entity type it does not support or understand - * it MUST respond with a 415 (Unsupported Media Type) status code." - */ -public class MkcolComplianceFilter implements HttpFilter { - - private static final String METHOD_MKCOL = "MKCOL"; - private static final String HEADER_TRANSFER_ENCODING = "Transfer-Encoding"; - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - // no-op - } - - @Override - public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { - boolean hasBody = request.getContentLengthLong() > 0 || request.getHeader(HEADER_TRANSFER_ENCODING) != null; - if (METHOD_MKCOL.equalsIgnoreCase(request.getMethod()) && hasBody) { - response.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE, "MKCOL with body not supported."); - } else { - chain.doFilter(request, response); - } - } - - @Override - public void destroy() { - // no-op - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/PostFromAllowHeaderRemovingHttpServletResponseWrapper.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/PostFromAllowHeaderRemovingHttpServletResponseWrapper.java deleted file mode 100644 index 8e2751a1b..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/PostFromAllowHeaderRemovingHttpServletResponseWrapper.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.cryptomator.frontend.webdav.filters; - -import static java.util.Arrays.stream; -import static java.util.function.Predicate.isEqual; -import static java.util.stream.Collectors.joining; - -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpServletResponseWrapper; - -class PostFromAllowHeaderRemovingHttpServletResponseWrapper extends HttpServletResponseWrapper { - - public PostFromAllowHeaderRemovingHttpServletResponseWrapper(HttpServletResponse response) { - super(response); - } - - @Override - public void addHeader(String name, String value) { - if (isAllowHeader(name)) { - super.setHeader(name, removePost(value)); - } else { - super.addHeader(name, value); - } - } - - @Override - public void setHeader(String name, String value) { - if (isAllowHeader(name)) { - super.setHeader(name, removePost(value)); - } else { - super.setHeader(name, value); - } - } - - private String removePost(String value) { - return stream(value.split("\\s*,\\s*")) - .filter(isEqual("POST").negate()) - .collect(joining(", ")); - } - - private boolean isAllowHeader(String name) { - return "allow".equalsIgnoreCase(name); - } -} \ No newline at end of file diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/PostRequestBlockingFilter.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/PostRequestBlockingFilter.java deleted file mode 100644 index 1c948e5db..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/PostRequestBlockingFilter.java +++ /dev/null @@ -1,50 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav.filters; - -import java.io.IOException; - -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * Blocks all post requests. - */ -public class PostRequestBlockingFilter implements HttpFilter { - - private static final String POST_METHOD = "POST"; - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - // no-op - } - - @Override - public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) - throws IOException, ServletException { - if (isPost(request)) { - response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); - } else { - chain.doFilter(request, new PostFromAllowHeaderRemovingHttpServletResponseWrapper(response)); - } - } - - private boolean isPost(HttpServletRequest request) { - return POST_METHOD.equalsIgnoreCase(request.getMethod()); - } - - @Override - public void destroy() { - // no-op - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/UriNormalizationFilter.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/UriNormalizationFilter.java deleted file mode 100644 index 953b341b5..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/filters/UriNormalizationFilter.java +++ /dev/null @@ -1,196 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav.filters; - -import java.io.IOException; -import java.net.URI; - -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletRequestWrapper; -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.StringUtils; -import org.cryptomator.frontend.webdav.filters.UriNormalizationFilter.ResourceTypeChecker.ResourceType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Normalizes all URIs contained in requests depending on the resource type of existing resources. - * URIs identifying directories will always end on "/", URIs identifying files will not. - * - * If the resource type is unknown, because the resource doesn't exist yet, this filter will determine the resource type based on the HTTP method, - * e.g. a MKCOL request will result in a directory.. - */ -public class UriNormalizationFilter implements HttpFilter { - - private static final Logger LOG = LoggerFactory.getLogger(UriNormalizationFilter.class); - private static final String[] FILE_METHODS = {"PUT"}; - private static final String[] DIRECTORY_METHODS = {"MKCOL"}; - - @FunctionalInterface - public interface ResourceTypeChecker { - - enum ResourceType { - FILE, FOLDER, UNKNOWN; - }; - - /** - * Checks if the resource with the given resource name is a file, a folder or doesn't exist. - * - * @param resourcePath Relative URI of the resource in question. - * @return Type of the resource or {@link ResourceType#UNKNOWN UNKNOWN} for non-existing resources. Never null. - */ - ResourceType typeOfResource(String resourcePath); - - } - - private final ResourceTypeChecker resourceTypeChecker; - private String contextPath; - - public UriNormalizationFilter(ResourceTypeChecker resourceTypeChecker) { - this.resourceTypeChecker = resourceTypeChecker; - } - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - contextPath = filterConfig.getServletContext().getContextPath(); - } - - @Override - public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { - final ResourceType resourceType = resourceTypeChecker.typeOfResource(request.getPathInfo()); - final HttpServletRequest normalizedRequest; - switch (resourceType) { - case FILE: - normalizedRequest = normalizedFileRequest(request); - break; - case FOLDER: - normalizedRequest = normalizedFolderRequest(request); - break; - default: - normalizedRequest = normalizedRequestForUnknownResource(request); - break; - } - chain.doFilter(normalizedRequest, response); - } - - @Override - public void destroy() { - // no-op - } - - private HttpServletRequest normalizedFileRequest(HttpServletRequest originalRequest) { - LOG.trace("Treating resource as file: {}", originalRequest.getRequestURI()); - return new FileUriRequest(originalRequest); - } - - private HttpServletRequest normalizedFolderRequest(HttpServletRequest originalRequest) { - LOG.trace("Treating resource as folder: {}", originalRequest.getRequestURI()); - return new FolderUriRequest(originalRequest); - } - - private HttpServletRequest normalizedRequestForUnknownResource(HttpServletRequest originalRequest) { - final String requestMethod = originalRequest.getMethod().toUpperCase(); - if (ArrayUtils.contains(FILE_METHODS, requestMethod)) { - return normalizedFileRequest(originalRequest); - } else if (ArrayUtils.contains(DIRECTORY_METHODS, requestMethod)) { - return normalizedFolderRequest(originalRequest); - } else { - LOG.trace("Could not determine resource type of resource: {}", originalRequest.getRequestURI()); - return originalRequest; - } - } - - /** - * Adjusts headers containing URIs depending on the request URI. - */ - private class SuffixPreservingRequest extends HttpServletRequestWrapper { - - private static final String HEADER_DESTINATION = "Destination"; - private static final String METHOD_MOVE = "MOVE"; - private static final String METHOD_COPY = "COPY"; - - public SuffixPreservingRequest(HttpServletRequest request) { - super(request); - request.getContextPath(); - } - - @Override - public String getHeader(String name) { - if ((METHOD_MOVE.equalsIgnoreCase(getMethod()) || METHOD_COPY.equalsIgnoreCase(getMethod())) && HEADER_DESTINATION.equalsIgnoreCase(name)) { - return bestGuess(URI.create(super.getHeader(name))); - } else { - return super.getHeader(name); - } - } - - private String bestGuess(URI uri) { - final String pathWithinContext = StringUtils.removeStart(uri.getPath(), contextPath); - final ResourceType resourceType = resourceTypeChecker.typeOfResource(pathWithinContext); - switch (resourceType) { - case FILE: - return asFileUri(uri.getRawPath()); - case FOLDER: - return asFolderUri(uri.getRawPath()); - default: - if (this.getRequestURI().endsWith("/")) { - return asFolderUri(uri.getRawPath()); - } else { - return asFileUri(uri.getRawPath()); - } - } - } - - protected String asFileUri(String uri) { - return StringUtils.removeEnd(uri, "/"); - } - - protected String asFolderUri(String uri) { - return StringUtils.appendIfMissing(uri, "/"); - } - - } - - /** - * HTTP request, whose URI never ends on "/". - */ - private class FileUriRequest extends SuffixPreservingRequest { - - public FileUriRequest(HttpServletRequest request) { - super(request); - } - - @Override - public String getRequestURI() { - return asFileUri(super.getRequestURI()); - } - - } - - /** - * HTTP request, whose URI always ends on "/". - */ - private class FolderUriRequest extends SuffixPreservingRequest { - - public FolderUriRequest(HttpServletRequest request) { - super(request); - } - - @Override - public String getRequestURI() { - return asFolderUri(super.getRequestURI()); - } - - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/DavFile.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/DavFile.java deleted file mode 100644 index 9c5d0ee8f..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/DavFile.java +++ /dev/null @@ -1,184 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.frontend.webdav.jackrabbitservlet; - -import java.io.IOException; -import java.nio.channels.Channels; -import java.nio.channels.WritableByteChannel; -import java.time.Instant; -import java.util.Optional; - -import org.apache.jackrabbit.webdav.DavException; -import org.apache.jackrabbit.webdav.DavResource; -import org.apache.jackrabbit.webdav.DavResourceIterator; -import org.apache.jackrabbit.webdav.DavServletResponse; -import org.apache.jackrabbit.webdav.DavSession; -import org.apache.jackrabbit.webdav.io.InputContext; -import org.apache.jackrabbit.webdav.io.OutputContext; -import org.apache.jackrabbit.webdav.lock.ActiveLock; -import org.apache.jackrabbit.webdav.lock.LockInfo; -import org.apache.jackrabbit.webdav.lock.LockManager; -import org.apache.jackrabbit.webdav.property.DavProperty; -import org.apache.jackrabbit.webdav.property.DavPropertyName; -import org.apache.jackrabbit.webdav.property.DavPropertySet; -import org.apache.jackrabbit.webdav.property.DefaultDavProperty; -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.filesystem.jackrabbit.FileLocator; - -import com.google.common.io.ByteStreams; - -class DavFile extends DavNode { - - protected static final String CONTENT_TYPE_VALUE = "application/octet-stream"; - protected static final String CONTENT_DISPOSITION_HEADER = "Content-Disposition"; - protected static final String CONTENT_DISPOSITION_VALUE = "attachment"; - protected static final String X_CONTENT_TYPE_OPTIONS_HEADER = "X-Content-Type-Options"; - protected static final String X_CONTENT_TYPE_OPTIONS_VALUE = "nosniff"; - - public DavFile(FilesystemResourceFactory factory, LockManager lockManager, DavSession session, FileLocator node) { - super(factory, lockManager, session, node); - } - - @Override - public boolean isCollection() { - return false; - } - - @Override - public void spool(OutputContext outputContext) throws IOException { - outputContext.setModificationTime(node.lastModified().toEpochMilli()); - if (!outputContext.hasStream()) { - return; - } - outputContext.setContentType(CONTENT_TYPE_VALUE); - outputContext.setProperty(CONTENT_DISPOSITION_HEADER, CONTENT_DISPOSITION_VALUE); - outputContext.setProperty(X_CONTENT_TYPE_OPTIONS_HEADER, X_CONTENT_TYPE_OPTIONS_VALUE); - outputContext.setContentLength(node.size()); - try (ReadableFile src = node.openReadable(); WritableByteChannel dst = Channels.newChannel(outputContext.getOutputStream())) { - ByteStreams.copy(src, dst); - } - } - - @Override - public void addMember(DavResource resource, InputContext inputContext) throws DavException { - throw new UnsupportedOperationException(); - } - - @Override - public DavResourceIterator getMembers() { - throw new UnsupportedOperationException(); - } - - @Override - public void removeMember(DavResource member) throws DavException { - throw new UnsupportedOperationException(); - } - - @Override - public void move(DavResource destination) throws DavException { - if (destination instanceof DavFile) { - DavFile dst = (DavFile) destination; - if (dst.node.exists()) { - // Overwrite header already checked by AbstractWebdavServlet#validateDestination - dst.node.delete(); - } else if (!dst.node.parent().get().exists()) { - throw new DavException(DavServletResponse.SC_CONFLICT, "Destination's parent doesn't exist."); - } - node.moveTo(dst.node); - } else if (destination instanceof DavFolder) { - DavFolder dst = (DavFolder) destination; - Folder parent = dst.node.parent().get(); - File newDst = parent.file(dst.node.name()); - if (dst.node.exists()) { - dst.node.delete(); - } else if (!parent.exists()) { - throw new DavException(DavServletResponse.SC_CONFLICT, "Destination's parent doesn't exist."); - } - node.moveTo(newDst); - } else { - throw new IllegalArgumentException("Destination not a DavFolder: " + destination.getClass().getName()); - } - } - - @Override - public void copy(DavResource destination, boolean shallow) throws DavException { - if (destination instanceof DavFile) { - DavFile dst = (DavFile) destination; - if (dst.node.exists()) { - // Overwrite header already checked by AbstractWebdavServlet#validateDestination - dst.node.delete(); - } else if (!dst.node.parent().get().exists()) { - throw new DavException(DavServletResponse.SC_CONFLICT, "Destination's parent doesn't exist."); - } - node.copyTo(dst.node); - } else if (destination instanceof DavFolder) { - DavFolder dst = (DavFolder) destination; - Folder parent = dst.node.parent().get(); - File newDst = parent.file(dst.node.name()); - if (dst.node.exists()) { - dst.node.delete(); - } else if (!parent.exists()) { - throw new DavException(DavServletResponse.SC_CONFLICT, "Destination's parent doesn't exist."); - } - node.copyTo(newDst); - } else { - throw new IllegalArgumentException("Destination not a DavFile: " + destination.getClass().getName()); - } - } - - @Override - public DavProperty getProperty(DavPropertyName name) { - if (DavPropertyName.GETCONTENTLENGTH.equals(name)) { - return sizeProperty().orElse(null); - } else { - return super.getProperty(name); - } - } - - @Override - public DavPropertySet getProperties() { - final DavPropertySet result = super.getProperties(); - if (!result.contains(DavPropertyName.GETCONTENTLENGTH)) { - sizeProperty().ifPresent(result::add); - } - return result; - } - - private Optional> sizeProperty() { - if (node.exists()) { - return Optional.of(new DefaultDavProperty(DavPropertyName.GETCONTENTLENGTH, node.size())); - } else { - return Optional.empty(); - } - } - - @Override - protected void setModificationTime(Instant lastModified) { - node.setLastModified(lastModified); - } - - @Override - protected void setCreationTime(Instant creationTime) { - node.setCreationTime(creationTime); - } - - @Override - public ActiveLock lock(LockInfo reqLockInfo) throws DavException { - ActiveLock lock = super.lock(reqLockInfo); - if (!exists()) { - DavFolder parentFolder = getCollection(); - assert parentFolder != null : "File always has a folder."; - parentFolder.addMember(this, new NullInputContext()); - } - return lock; - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/DavFileWithRange.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/DavFileWithRange.java deleted file mode 100644 index 8151b96c3..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/DavFileWithRange.java +++ /dev/null @@ -1,91 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav.jackrabbitservlet; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.channels.Channels; -import java.util.Objects; - -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.apache.commons.lang3.tuple.Pair; -import org.apache.jackrabbit.webdav.DavException; -import org.apache.jackrabbit.webdav.DavServletResponse; -import org.apache.jackrabbit.webdav.DavSession; -import org.apache.jackrabbit.webdav.io.OutputContext; -import org.apache.jackrabbit.webdav.lock.LockManager; -import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.filesystem.jackrabbit.FileLocator; -import org.eclipse.jetty.http.HttpHeader; - -import com.google.common.io.ByteStreams; - -/** - * Delivers only the requested range of bytes from a file. - * - * @see {@link https://tools.ietf.org/html/rfc7233#section-4} - */ -class DavFileWithRange extends DavFile { - - private final Pair requestRange; - - public DavFileWithRange(FilesystemResourceFactory factory, LockManager lockManager, DavSession session, FileLocator node, Pair requestRange) throws DavException { - super(factory, lockManager, session, node); - this.requestRange = Objects.requireNonNull(requestRange); - } - - @Override - public void spool(OutputContext outputContext) throws IOException { - outputContext.setModificationTime(node.lastModified().toEpochMilli()); - if (!outputContext.hasStream()) { - return; - } - final long contentLength = node.size(); - try (ReadableFile src = node.openReadable(); OutputStream out = outputContext.getOutputStream()) { - final Pair range = getEffectiveRange(contentLength); - if (range.getLeft() < 0 || range.getLeft() > range.getRight() || range.getRight() > contentLength) { - outputContext.setProperty(HttpHeader.CONTENT_RANGE.asString(), "bytes */" + contentLength); - throw new UncheckedDavException(DavServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE, "Valid Range would be in [0, " + contentLength + "]"); - } - final Long rangeLength = range.getRight() - range.getLeft() + 1; - outputContext.setContentLength(rangeLength); - outputContext.setProperty(HttpHeader.CONTENT_RANGE.asString(), contentRangeResponseHeader(range.getLeft(), range.getRight(), contentLength)); - outputContext.setContentType(CONTENT_TYPE_VALUE); - outputContext.setProperty(CONTENT_DISPOSITION_HEADER, CONTENT_DISPOSITION_VALUE); - outputContext.setProperty(X_CONTENT_TYPE_OPTIONS_HEADER, X_CONTENT_TYPE_OPTIONS_VALUE); - src.position(range.getLeft()); - InputStream limitedIn = ByteStreams.limit(Channels.newInputStream(src), rangeLength); - ByteStreams.copy(limitedIn, out); - } - } - - private String contentRangeResponseHeader(long firstByte, long lastByte, long completeLength) { - return String.format("bytes %d-%d/%d", firstByte, lastByte, completeLength); - } - - private Pair getEffectiveRange(long contentLength) { - try { - final Long lower = requestRange.getLeft().isEmpty() ? null : Long.valueOf(requestRange.getLeft()); - final Long upper = requestRange.getRight().isEmpty() ? null : Long.valueOf(requestRange.getRight()); - if (lower == null && upper == null) { - return new ImmutablePair(0l, contentLength - 1); - } else if (lower == null) { - return new ImmutablePair(contentLength - upper, contentLength - 1); - } else if (upper == null) { - return new ImmutablePair(lower, contentLength - 1); - } else { - return new ImmutablePair(lower, Math.min(upper, contentLength - 1)); - } - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Invalid byte range: " + requestRange, e); - } - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/DavFileWithUnsatisfiableRange.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/DavFileWithUnsatisfiableRange.java deleted file mode 100644 index 1b820f90e..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/DavFileWithUnsatisfiableRange.java +++ /dev/null @@ -1,50 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav.jackrabbitservlet; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.channels.Channels; - -import org.apache.jackrabbit.webdav.DavException; -import org.apache.jackrabbit.webdav.DavSession; -import org.apache.jackrabbit.webdav.io.OutputContext; -import org.apache.jackrabbit.webdav.lock.LockManager; -import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.filesystem.jackrabbit.FileLocator; -import org.eclipse.jetty.http.HttpHeader; - -import com.google.common.io.ByteStreams; - -/** - * Sends the full file in reaction to an unsatisfiable range. - * - * @see {@link https://tools.ietf.org/html/rfc7233#section-4.2} - */ -class DavFileWithUnsatisfiableRange extends DavFile { - - public DavFileWithUnsatisfiableRange(FilesystemResourceFactory factory, LockManager lockManager, DavSession session, FileLocator node) throws DavException { - super(factory, lockManager, session, node); - } - - @Override - public void spool(OutputContext outputContext) throws IOException { - outputContext.setModificationTime(node.lastModified().toEpochMilli()); - if (!outputContext.hasStream()) { - return; - } - final long contentLength = node.size(); - outputContext.setContentLength(contentLength); - outputContext.setProperty(HttpHeader.CONTENT_RANGE.asString(), "bytes */" + contentLength); - try (ReadableFile src = node.openReadable(); OutputStream out = outputContext.getOutputStream()) { - ByteStreams.copy(src, Channels.newChannel(out)); - } - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/DavFolder.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/DavFolder.java deleted file mode 100644 index fe72d862a..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/DavFolder.java +++ /dev/null @@ -1,217 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.frontend.webdav.jackrabbitservlet; - -import java.io.IOException; -import java.io.InputStream; -import java.io.UncheckedIOException; -import java.nio.channels.Channels; -import java.nio.channels.ReadableByteChannel; -import java.time.Instant; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.apache.commons.lang3.ArrayUtils; -import org.apache.jackrabbit.webdav.DavException; -import org.apache.jackrabbit.webdav.DavResource; -import org.apache.jackrabbit.webdav.DavResourceIterator; -import org.apache.jackrabbit.webdav.DavResourceIteratorImpl; -import org.apache.jackrabbit.webdav.DavServletResponse; -import org.apache.jackrabbit.webdav.DavSession; -import org.apache.jackrabbit.webdav.io.InputContext; -import org.apache.jackrabbit.webdav.io.OutputContext; -import org.apache.jackrabbit.webdav.lock.ActiveLock; -import org.apache.jackrabbit.webdav.lock.LockManager; -import org.apache.jackrabbit.webdav.property.DavProperty; -import org.apache.jackrabbit.webdav.property.DavPropertyName; -import org.apache.jackrabbit.webdav.property.DefaultDavProperty; -import org.apache.jackrabbit.webdav.property.ResourceType; -import org.cryptomator.common.streams.AutoClosingStream; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.Node; -import org.cryptomator.filesystem.WritableFile; -import org.cryptomator.filesystem.jackrabbit.FileLocator; -import org.cryptomator.filesystem.jackrabbit.FolderLocator; - -import com.google.common.io.ByteStreams; - -class DavFolder extends DavNode { - - private static final DavPropertyName PROPERTY_QUOTA_AVAILABLE = DavPropertyName.create("quota-available-bytes"); - private static final DavPropertyName PROPERTY_QUOTA_USED = DavPropertyName.create("quota-used-bytes"); - - public DavFolder(FilesystemResourceFactory factory, LockManager lockManager, DavSession session, FolderLocator folder) { - super(factory, lockManager, session, folder); - properties.add(new ResourceType(ResourceType.COLLECTION)); - properties.add(new DefaultDavProperty(DavPropertyName.ISCOLLECTION, 1)); - } - - @Override - public boolean isCollection() { - return true; - } - - @Override - public void spool(OutputContext outputContext) throws IOException { - // no-op - } - - @Override - public void addMember(DavResource resource, InputContext inputContext) throws DavException { - if (resource instanceof DavFolder) { - addMemberFolder((DavFolder) resource); - } else if (resource instanceof DavFile) { - assert inputContext.hasStream(); - addMemberFile((DavFile) resource, inputContext.getInputStream()); - } else { - throw new IllegalArgumentException("Unsupported resource type: " + resource.getClass().getName()); - } - } - - private void addMemberFolder(DavFolder memberFolder) { - node.folder(memberFolder.getDisplayName()).create(); - } - - private void addMemberFile(DavFile memberFile, InputStream inputStream) { - try (ReadableByteChannel src = Channels.newChannel(inputStream); WritableFile dst = node.file(memberFile.getDisplayName()).openWritable()) { - dst.truncate(); - ByteStreams.copy(src, dst); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - @Override - public DavResourceIterator getMembers() { - final Stream folders = node.folders().map(this::folderToDavFolder); - final Stream files = node.files().map(this::fileToDavFile); - final Stream members = AutoClosingStream.from(Stream.concat(folders, files)); - return new DavResourceIteratorImpl(members.collect(Collectors.toList())); - } - - private DavFolder folderToDavFolder(FolderLocator memberFolder) { - return factory.createFolder(memberFolder, session); - } - - private DavFile fileToDavFile(FileLocator memberFile) { - return factory.createFile(memberFile, session); - } - - @Override - public void removeMember(DavResource member) throws DavException { - for (ActiveLock lock : member.getLocks()) { - member.unlock(lock.getToken()); - } - final Node child = getMemberNode(member.getDisplayName()); - child.delete(); - } - - /** - * @throws DavException - * Error 404 if no child with the given name exists - */ - private Node getMemberNode(String name) throws DavException { - Node file = node.file(name); - Node folder = node.folder(name); - if (file.exists()) { - return file; - } else if (folder.exists()) { - return folder; - } else { - throw new DavException(DavServletResponse.SC_NOT_FOUND, "No such file or directory: " + node.getResourcePath() + name); - } - } - - @Override - public void move(DavResource destination) throws DavException { - if (destination instanceof DavFolder) { - DavFolder dst = (DavFolder) destination; - if (dst.node.exists()) { - dst.node.delete(); - } else if (!dst.node.parent().get().exists()) { - throw new DavException(DavServletResponse.SC_CONFLICT, "Destination's parent doesn't exist."); - } - node.moveTo(dst.node); - } else if (destination instanceof DavFile) { - DavFile dst = (DavFile) destination; - Folder parent = dst.node.parent().get(); - Folder newDst = parent.folder(dst.node.name()); - if (dst.node.exists()) { - dst.node.delete(); - } else if (!parent.exists()) { - throw new DavException(DavServletResponse.SC_CONFLICT, "Destination's parent doesn't exist."); - } - node.moveTo(newDst); - } else { - throw new IllegalArgumentException("Destination not a DavFolder: " + destination.getClass().getName()); - } - } - - @Override - public void copy(DavResource destination, boolean shallow) throws DavException { - if (!node.exists()) { - throw new DavException(DavServletResponse.SC_NOT_FOUND); - } - if (destination instanceof DavNode) { - DavNode dst = (DavNode) destination; - if (!dst.node.parent().get().exists()) { - throw new DavException(DavServletResponse.SC_CONFLICT, "Destination's parent doesn't exist."); - } - } - if (destination instanceof DavFolder) { - DavFolder dst = (DavFolder) destination; - if (shallow) { - // create destination, if it doesn't exist yet: - dst.node.create(); - // http://www.webdav.org/specs/rfc2518.html#copy.for.collections - node.creationTime().ifPresent(dst::setCreationTime); - dst.setModificationTime(node.lastModified()); - } else { - node.copyTo(dst.node); - } - } else if (destination instanceof DavFile) { - DavFile dst = (DavFile) destination; - Folder parent = dst.node.parent().get(); - Folder newDst = parent.folder(dst.node.name()); - if (dst.node.exists()) { - dst.node.delete(); - } - node.copyTo(newDst); - } else { - throw new IllegalArgumentException("Destination not a DavFolder: " + destination.getClass().getName()); - } - } - - @Override - protected void setModificationTime(Instant instant) { - node.setLastModified(instant); - } - - @Override - protected void setCreationTime(Instant instant) { - node.setCreationTime(instant); - } - - @Override - public DavPropertyName[] getPropertyNames() { - return ArrayUtils.addAll(super.getPropertyNames(), PROPERTY_QUOTA_AVAILABLE, PROPERTY_QUOTA_USED); - } - - @Override - public DavProperty getProperty(DavPropertyName name) { - if (PROPERTY_QUOTA_AVAILABLE.equals(name)) { - return node.fileSystem().quotaAvailableBytes().map(numBytes -> new DefaultDavProperty(name, numBytes)).orElse(null); - } else if (PROPERTY_QUOTA_USED.equals(name)) { - return node.fileSystem().quotaUsedBytes().map(numBytes -> new DefaultDavProperty(name, numBytes)).orElse(null); - } else { - return super.getProperty(name); - } - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/DavNode.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/DavNode.java deleted file mode 100644 index 6cb537b9e..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/DavNode.java +++ /dev/null @@ -1,253 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.frontend.webdav.jackrabbitservlet; - -import java.io.UncheckedIOException; -import java.time.Instant; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.time.temporal.Temporal; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.stream.Stream; - -import org.apache.jackrabbit.webdav.DavConstants; -import org.apache.jackrabbit.webdav.DavException; -import org.apache.jackrabbit.webdav.DavResource; -import org.apache.jackrabbit.webdav.DavSession; -import org.apache.jackrabbit.webdav.MultiStatusResponse; -import org.apache.jackrabbit.webdav.lock.ActiveLock; -import org.apache.jackrabbit.webdav.lock.LockInfo; -import org.apache.jackrabbit.webdav.lock.LockManager; -import org.apache.jackrabbit.webdav.lock.Scope; -import org.apache.jackrabbit.webdav.lock.Type; -import org.apache.jackrabbit.webdav.property.DavProperty; -import org.apache.jackrabbit.webdav.property.DavPropertyName; -import org.apache.jackrabbit.webdav.property.DavPropertyNameSet; -import org.apache.jackrabbit.webdav.property.DavPropertySet; -import org.apache.jackrabbit.webdav.property.DefaultDavProperty; -import org.apache.jackrabbit.webdav.property.PropEntry; -import org.cryptomator.filesystem.jackrabbit.FileSystemResourceLocator; -import org.cryptomator.filesystem.jackrabbit.FolderLocator; - -abstract class DavNode implements DavResource { - - private static final String DAV_COMPLIANCE_CLASSES = "1, 2"; - private static final String[] DAV_CREATIONDATE_PROPNAMES = {DavConstants.PROPERTY_CREATIONDATE, "Win32CreationTime"}; - private static final String[] DAV_MODIFIEDDATE_PROPNAMES = {DavConstants.PROPERTY_GETLASTMODIFIED, "Win32LastModifiedTime"}; - - protected final FilesystemResourceFactory factory; - protected final LockManager lockManager; - protected final DavSession session; - protected final T node; - protected final DavPropertySet properties; - - public DavNode(FilesystemResourceFactory factory, LockManager lockManager, DavSession session, T node) { - this.factory = factory; - this.lockManager = lockManager; - this.session = session; - this.node = node; - this.properties = new DavPropertySet(); - } - - @Override - public String getComplianceClass() { - return DAV_COMPLIANCE_CLASSES; - } - - @Override - public String getSupportedMethods() { - return METHODS; - } - - @Override - public boolean exists() { - return node.exists(); - } - - @Override - public String getDisplayName() { - return node.name(); - } - - @Override - public FileSystemResourceLocator getLocator() { - return node; - } - - @Override - public String getResourcePath() { - return node.getResourcePath(); - } - - @Override - public String getHref() { - return node.getHref(); - } - - @Override - public long getModificationTime() { - try { - return node.lastModified().toEpochMilli(); - } catch (UncheckedIOException e) { - return -1l; - } - } - - protected abstract void setModificationTime(Instant instant); - - protected abstract void setCreationTime(Instant instant); - - @Override - public DavPropertyName[] getPropertyNames() { - return getProperties().getPropertyNames(); - } - - @Override - public DavProperty getProperty(DavPropertyName name) { - final String namespacelessPropertyName = name.getName(); - if (Arrays.asList(DAV_CREATIONDATE_PROPNAMES).contains(namespacelessPropertyName)) { - return creationDateProperty(name).orElse(null); - } else if (Arrays.asList(DAV_MODIFIEDDATE_PROPNAMES).contains(namespacelessPropertyName)) { - Temporal lastModifiedDate = OffsetDateTime.ofInstant(node.lastModified(), ZoneOffset.UTC); - return new DefaultDavProperty<>(name, DateTimeFormatter.RFC_1123_DATE_TIME.format(lastModifiedDate)); - } else { - return properties.get(name); - } - } - - /** - * Returns a current snapshot of all available properties. - */ - @Override - public DavPropertySet getProperties() { - // creation date: - if (node.exists()) { - creationDateProperty(DavPropertyName.CREATIONDATE).ifPresent(properties::add); - } - // modification date: - if (node.exists()) { - Temporal lastModifiedDate = OffsetDateTime.ofInstant(node.lastModified(), ZoneOffset.UTC); - String lastModifiedDateStr = DateTimeFormatter.RFC_1123_DATE_TIME.format(lastModifiedDate); - DavProperty lastModifiedDateProp = new DefaultDavProperty<>(DavPropertyName.GETLASTMODIFIED, lastModifiedDateStr); - properties.add(lastModifiedDateProp); - } - return properties; - } - - private Optional> creationDateProperty(DavPropertyName name) { - return node.creationTime() // - .map(creationTime -> OffsetDateTime.ofInstant(creationTime, ZoneOffset.UTC)) // - .map(creationDate -> new DefaultDavProperty<>(name, DateTimeFormatter.RFC_1123_DATE_TIME.format(creationDate))); - } - - @Override - public void setProperty(DavProperty property) throws DavException { - final String namespacelessPropertyName = property.getName().getName(); - if (Arrays.asList(DAV_CREATIONDATE_PROPNAMES).contains(namespacelessPropertyName) && property.getValue() instanceof String) { - String createDateStr = (String) property.getValue(); - OffsetDateTime creationDate = OffsetDateTime.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(createDateStr)); - this.setCreationTime(creationDate.toInstant()); - } else if (Arrays.asList(DAV_MODIFIEDDATE_PROPNAMES).contains(namespacelessPropertyName) && property.getValue() instanceof String) { - String lastModifiedDateStr = (String) property.getValue(); - OffsetDateTime lastModifiedDate = OffsetDateTime.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(lastModifiedDateStr)); - this.setModificationTime(lastModifiedDate.toInstant()); - } - properties.add(property); - } - - @Override - public void removeProperty(DavPropertyName propertyName) throws DavException { - getProperties().remove(propertyName); - } - - @Override - public MultiStatusResponse alterProperties(List changeList) throws DavException { - final DavPropertyNameSet names = new DavPropertyNameSet(); - for (final PropEntry entry : changeList) { - if (entry instanceof DavProperty) { - final DavProperty prop = (DavProperty) entry; - this.setProperty(prop); - names.add(prop.getName()); - } else if (entry instanceof DavPropertyName) { - final DavPropertyName name = (DavPropertyName) entry; - this.removeProperty(name); - names.add(name); - } - } - return new MultiStatusResponse(this, names); - } - - @Override - public DavFolder getCollection() { - if (node.isRootLocation()) { - return null; - } - - assert node.parent().isPresent() : "as my mom always sais: if it's not root, it has a parent"; - final FolderLocator parentPath = node.parent().get(); - return factory.createFolder(parentPath, session); - } - - @Override - public boolean isLockable(Type type, Scope scope) { - return Type.WRITE.equals(type) && Scope.EXCLUSIVE.equals(scope) || Scope.SHARED.equals(scope); - } - - @Override - public boolean hasLock(Type type, Scope scope) { - return getLock(type, scope) != null; - } - - @Override - public ActiveLock getLock(Type type, Scope scope) { - return lockManager.getLock(type, scope, this); - } - - @Override - public ActiveLock[] getLocks() { - final ActiveLock exclusiveWriteLock = getLock(Type.WRITE, Scope.EXCLUSIVE); - final ActiveLock sharedWriteLock = getLock(Type.WRITE, Scope.SHARED); - return Stream.of(exclusiveWriteLock, sharedWriteLock).filter(Objects::nonNull).toArray(ActiveLock[]::new); - } - - @Override - public ActiveLock lock(LockInfo reqLockInfo) throws DavException { - return lockManager.createLock(reqLockInfo, this); - } - - @Override - public ActiveLock refreshLock(LockInfo reqLockInfo, String lockToken) throws DavException { - return lockManager.refreshLock(reqLockInfo, lockToken, this); - } - - @Override - public void unlock(String lockToken) throws DavException { - lockManager.releaseLock(lockToken, this); - } - - @Override - public void addLockManager(LockManager lockmgr) { - throw new UnsupportedOperationException("Locks are managed"); - } - - @Override - public FilesystemResourceFactory getFactory() { - return factory; - } - - @Override - public DavSession getSession() { - return session; - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/DavSessionImpl.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/DavSessionImpl.java deleted file mode 100644 index 356a84794..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/DavSessionImpl.java +++ /dev/null @@ -1,45 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014, 2016 Sebastian Stenzel - * 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.frontend.webdav.jackrabbitservlet; - -import java.util.HashSet; - -import org.apache.jackrabbit.webdav.DavSession; - -class DavSessionImpl implements DavSession { - - private final HashSet lockTokens = new HashSet(); - private final HashSet references = new HashSet(); - - @Override - public void addReference(Object reference) { - references.add(reference); - } - - @Override - public void removeReference(Object reference) { - references.remove(reference); - } - - @Override - public void addLockToken(String token) { - lockTokens.add(token); - } - - @Override - public String[] getLockTokens() { - return lockTokens.toArray(new String[lockTokens.size()]); - } - - @Override - public void removeLockToken(String token) { - lockTokens.remove(token); - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/DavSessionProviderImpl.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/DavSessionProviderImpl.java deleted file mode 100644 index 7f626ca1a..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/DavSessionProviderImpl.java +++ /dev/null @@ -1,36 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014, 2016 Sebastian Stenzel - * 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.frontend.webdav.jackrabbitservlet; - -import org.apache.jackrabbit.webdav.DavException; -import org.apache.jackrabbit.webdav.DavSession; -import org.apache.jackrabbit.webdav.DavSessionProvider; -import org.apache.jackrabbit.webdav.WebdavRequest; - -class DavSessionProviderImpl implements DavSessionProvider { - - @Override - public boolean attachSession(WebdavRequest request) throws DavException { - // every request gets a new session - final DavSession session = new DavSessionImpl(); - session.addReference(request); - request.setDavSession(session); - return true; - } - - @Override - public void releaseSession(WebdavRequest request) { - final DavSession session = request.getDavSession(); - if (session != null) { - session.removeReference(request); - request.setDavSession(null); - } - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/ExclusiveSharedLock.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/ExclusiveSharedLock.java deleted file mode 100644 index 1b79afea6..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/ExclusiveSharedLock.java +++ /dev/null @@ -1,109 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav.jackrabbitservlet; - -import org.apache.jackrabbit.webdav.DavConstants; -import org.apache.jackrabbit.webdav.lock.AbstractActiveLock; -import org.apache.jackrabbit.webdav.lock.LockInfo; -import org.apache.jackrabbit.webdav.lock.Scope; -import org.apache.jackrabbit.webdav.lock.Type; - -class ExclusiveSharedLock extends AbstractActiveLock { - - private final String token; - private final Type type; - private final Scope scope; - private String owner; - private boolean isDeep = true; // deep by default - private long expirationTime = DavConstants.INFINITE_TIMEOUT; // never expires by default; - - ExclusiveSharedLock(String token, LockInfo lockInfo) { - this.token = token; - this.type = lockInfo.getType(); - this.scope = lockInfo.getScope(); - this.owner = lockInfo.getOwner(); - this.isDeep = lockInfo.isDeep(); - setTimeout(lockInfo.getTimeout()); - } - - @Override - public boolean isLockedByToken(String lockToken) { - return token.equals(lockToken); - } - - @Override - public boolean isExpired() { - return System.currentTimeMillis() > expirationTime; - } - - @Override - public String getToken() { - return token; - } - - @Override - public String getOwner() { - return owner; - } - - @Override - public void setOwner(String owner) { - this.owner = owner; - } - - @Override - public long getTimeout() { - return expirationTime - System.currentTimeMillis(); - } - - @Override - public void setTimeout(long timeout) { - if (timeout > 0) { - expirationTime = System.currentTimeMillis() + timeout; - } - } - - @Override - public boolean isDeep() { - return isDeep; - } - - @Override - public void setIsDeep(boolean isDeep) { - this.isDeep = isDeep; - } - - @Override - public Type getType() { - return type; - } - - @Override - public Scope getScope() { - return scope; - } - - /* HASHCODE / EQUALS */ - - @Override - public int hashCode() { - return getToken().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof ExclusiveSharedLock) { - ExclusiveSharedLock other = (ExclusiveSharedLock) obj; - return this.getToken().equals(other.getToken()); - } else { - return false; - } - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/ExclusiveSharedLockManager.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/ExclusiveSharedLockManager.java deleted file mode 100644 index 88225c855..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/ExclusiveSharedLockManager.java +++ /dev/null @@ -1,181 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav.jackrabbitservlet; - -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -import org.apache.jackrabbit.webdav.DavConstants; -import org.apache.jackrabbit.webdav.DavException; -import org.apache.jackrabbit.webdav.DavResource; -import org.apache.jackrabbit.webdav.DavServletResponse; -import org.apache.jackrabbit.webdav.lock.ActiveLock; -import org.apache.jackrabbit.webdav.lock.LockInfo; -import org.apache.jackrabbit.webdav.lock.LockManager; -import org.apache.jackrabbit.webdav.lock.Scope; -import org.apache.jackrabbit.webdav.lock.Type; -import org.cryptomator.filesystem.jackrabbit.FileSystemResourceLocator; -import org.cryptomator.filesystem.jackrabbit.FolderLocator; - -class ExclusiveSharedLockManager implements LockManager { - - private final ConcurrentMap> lockedResources = new ConcurrentHashMap<>(); - - @Override - public ActiveLock createLock(LockInfo lockInfo, DavResource resource) throws DavException { - Objects.requireNonNull(lockInfo); - Objects.requireNonNull(resource); - if (resource instanceof DavNode) { - return createLockInternal(lockInfo, (DavNode) resource); - } else { - throw new IllegalArgumentException("Unsupported resource type " + resource.getClass()); - } - } - - private synchronized ActiveLock createLockInternal(LockInfo lockInfo, DavNode resource) throws DavException { - FileSystemResourceLocator locator = resource.getLocator(); - removedExpiredLocksInLocatorHierarchy(locator); - - // look for existing locks on this resource or its ancestors: - ActiveLock existingExclusiveLock = getLock(lockInfo.getType(), Scope.EXCLUSIVE, resource); - ActiveLock existingSharedLock = getLock(lockInfo.getType(), Scope.SHARED, resource); - boolean hasExclusiveLock = existingExclusiveLock != null; - boolean hasSharedLock = existingSharedLock != null; - boolean isLocked = hasExclusiveLock || hasSharedLock; - if ((Scope.EXCLUSIVE.equals(lockInfo.getScope()) && isLocked) || (Scope.SHARED.equals(lockInfo.getScope()) && hasExclusiveLock)) { - throw new DavException(DavServletResponse.SC_LOCKED, "Resource (or parent resource) already locked."); - } - - // look for locked children: - for (Entry> potentialChild : lockedResources.entrySet()) { - final FileSystemResourceLocator childLocator = potentialChild.getKey(); - final Collection childLocks = potentialChild.getValue().values(); - if (isChild(locator, childLocator) && isAffectedByChildLocks(lockInfo, locator, childLocks, childLocator)) { - throw new DavException(DavServletResponse.SC_CONFLICT, "Subresource already locked. " + childLocator); - } - } - - String token = DavConstants.OPAQUE_LOCK_TOKEN_PREFIX + UUID.randomUUID(); - Map lockMap = Objects.requireNonNull(lockedResources.computeIfAbsent(locator, loc -> new HashMap<>())); - return lockMap.computeIfAbsent(token, t -> new ExclusiveSharedLock(t, lockInfo)); - } - - private void removedExpiredLocksInLocatorHierarchy(FileSystemResourceLocator locator) { - Objects.requireNonNull(locator); - lockedResources.getOrDefault(locator, Collections.emptyMap()).values().removeIf(ActiveLock::isExpired); - locator.parent().ifPresent(this::removedExpiredLocksInLocatorHierarchy); - } - - private boolean isChild(FileSystemResourceLocator parent, FileSystemResourceLocator child) { - if (parent instanceof FolderLocator) { - FolderLocator folder = (FolderLocator) parent; - return folder.isAncestorOf(child); - } else { - return false; - } - } - - private boolean isAffectedByChildLocks(LockInfo parentLockInfo, FileSystemResourceLocator parentLocator, Collection childLocks, FileSystemResourceLocator childLocator) { - assert childLocator.parent().isPresent(); - for (ActiveLock lock : childLocks) { - if (Scope.SHARED.equals(lock.getScope()) && Scope.SHARED.equals(parentLockInfo.getScope())) { - continue; - } else if (parentLockInfo.isDeep() || childLocator.parent().get().equals(parentLocator)) { - return true; - } - } - return false; - } - - @Override - public ActiveLock refreshLock(LockInfo lockInfo, String lockToken, DavResource resource) throws DavException { - ActiveLock lock = getLock(lockInfo.getType(), lockInfo.getScope(), resource); - if (lock == null) { - throw new DavException(DavServletResponse.SC_PRECONDITION_FAILED); - } else if (!lock.isLockedByToken(lockToken)) { - throw new DavException(DavServletResponse.SC_LOCKED); - } - lock.setTimeout(lockInfo.getTimeout()); - return lock; - } - - @Override - public synchronized void releaseLock(String lockToken, DavResource resource) throws DavException { - if (resource instanceof DavNode) { - try { - releaseLockInternal(lockToken, (DavNode) resource); - } catch (UncheckedDavException e) { - throw e.toDavException(); - } - } else { - throw new IllegalArgumentException("Unsupported resource type " + resource.getClass()); - } - } - - private synchronized void releaseLockInternal(String lockToken, DavNode resource) throws UncheckedDavException { - lockedResources.compute(resource.getLocator(), (loc, locks) -> { - if (locks == null || locks.isEmpty()) { - // no lock exists, nothing needs to change. - return null; - } else if (!locks.containsKey(lockToken)) { - throw new UncheckedDavException(DavServletResponse.SC_LOCKED, "Resource locked with different token."); - } else { - locks.remove(lockToken); - return locks.isEmpty() ? null : locks; - } - }); - } - - @Override - public ActiveLock getLock(Type type, Scope scope, DavResource resource) { - if (resource instanceof DavNode) { - return getLockInternal(type, scope, ((DavNode) resource).getLocator(), 0); - } else { - throw new IllegalArgumentException("Unsupported resource type " + resource.getClass()); - } - } - - private ActiveLock getLockInternal(Type type, Scope scope, FileSystemResourceLocator locator, int depth) { - // try to find a lock directly on this resource: - if (lockedResources.containsKey(locator)) { - for (ActiveLock lock : lockedResources.get(locator).values()) { - if (type.equals(lock.getType()) && scope.equals(lock.getScope()) && (depth == 0 || lock.isDeep())) { - return lock; - } - } - } - // or otherwise look for parent locks: - if (locator.parent().isPresent()) { - return getLockInternal(type, scope, locator.parent().get(), depth + 1); - } else { - return null; - } - } - - @Override - public boolean hasLock(String lockToken, DavResource resource) { - if (resource instanceof DavNode) { - return hasLockInternal(lockToken, (DavNode) resource); - } else { - throw new IllegalArgumentException("Unsupported resource type " + resource.getClass()); - } - } - - private boolean hasLockInternal(String lockToken, DavNode resource) { - return lockedResources.getOrDefault(resource.getLocator(), Collections.emptyMap()).containsKey(lockToken); - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/FilesystemResourceFactory.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/FilesystemResourceFactory.java deleted file mode 100644 index 43bce9663..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/FilesystemResourceFactory.java +++ /dev/null @@ -1,149 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.frontend.webdav.jackrabbitservlet; - -import java.time.Instant; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; - -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.apache.commons.lang3.tuple.Pair; -import org.apache.jackrabbit.webdav.DavException; -import org.apache.jackrabbit.webdav.DavMethods; -import org.apache.jackrabbit.webdav.DavResource; -import org.apache.jackrabbit.webdav.DavResourceFactory; -import org.apache.jackrabbit.webdav.DavResourceLocator; -import org.apache.jackrabbit.webdav.DavServletRequest; -import org.apache.jackrabbit.webdav.DavServletResponse; -import org.apache.jackrabbit.webdav.DavSession; -import org.apache.jackrabbit.webdav.lock.LockManager; -import org.cryptomator.filesystem.jackrabbit.FileLocator; -import org.cryptomator.filesystem.jackrabbit.FolderLocator; -import org.eclipse.jetty.http.HttpHeader; - -class FilesystemResourceFactory implements DavResourceFactory { - - private static final String RANGE_BYTE_PREFIX = "bytes="; - private static final char RANGE_SET_SEP = ','; - private static final char RANGE_SEP = '-'; - - private final LockManager lockManager; - - public FilesystemResourceFactory() { - this.lockManager = new ExclusiveSharedLockManager(); - } - - @Override - public DavResource createResource(DavResourceLocator locator, DavServletRequest request, DavServletResponse response) throws DavException { - if (locator instanceof FileLocator && DavMethods.METHOD_GET.equals(request.getMethod()) && request.getHeader(HttpHeader.RANGE.asString()) != null) { - return createFileRange((FileLocator) locator, request.getDavSession(), request, response); - } else { - return createResource(locator, request.getDavSession()); - } - } - - @Override - public DavResource createResource(DavResourceLocator locator, DavSession session) { - if (locator instanceof FolderLocator) { - FolderLocator folder = (FolderLocator) locator; - return createFolder(folder, session); - } else if (locator instanceof FileLocator) { - FileLocator file = (FileLocator) locator; - return createFile(file, session); - } else { - throw new IllegalArgumentException("Unsupported locator type " + locator.getClass().getName()); - } - } - - DavFolder createFolder(FolderLocator folder, DavSession session) { - return new DavFolder(this, lockManager, session, folder); - } - - DavFile createFile(FileLocator file, DavSession session) { - return new DavFile(this, lockManager, session, file); - } - - private DavFile createFileRange(FileLocator file, DavSession session, DavServletRequest request, DavServletResponse response) throws DavException { - // 404 for non-existing resources: - if (!file.exists()) { - throw new DavException(DavServletResponse.SC_NOT_FOUND); - } - - // 200 for "normal" resources, if if-range is not satisified: - final String ifRangeHeader = request.getHeader(HttpHeader.IF_RANGE.asString()); - if (!isIfRangeHeaderSatisfied(file, ifRangeHeader)) { - return createFile(file, session); - } - - final String rangeHeader = request.getHeader(HttpHeader.RANGE.asString()); - try { - // 206 for ranged resources: - final Pair parsedRange = parseRangeRequestHeader(rangeHeader); - response.setStatus(DavServletResponse.SC_PARTIAL_CONTENT); - return new DavFileWithRange(this, lockManager, session, file, parsedRange); - } catch (DavException ex) { - if (ex.getErrorCode() == DavServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE) { - // 416 for unsatisfiable ranges: - response.setStatus(DavServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); - return new DavFileWithUnsatisfiableRange(this, lockManager, session, file); - } else { - throw new DavException(ex.getErrorCode(), ex); - } - } - } - - /** - * Processes the given range header field, if it is supported. Only headers containing a single byte range are supported.
- * - * bytes=100-200
- * bytes=-500
- * bytes=1000- - *
- * - * @return Tuple of lower and upper range. - * @throws DavException HTTP statuscode 400 for malformed requests. 416 if requested range is not supported. - */ - private Pair parseRangeRequestHeader(String rangeHeader) throws DavException { - assert rangeHeader != null; - if (!rangeHeader.startsWith(RANGE_BYTE_PREFIX)) { - throw new DavException(DavServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); - } - final String byteRangeSet = StringUtils.removeStartIgnoreCase(rangeHeader, RANGE_BYTE_PREFIX); - final String[] byteRanges = StringUtils.split(byteRangeSet, RANGE_SET_SEP); - if (byteRanges.length != 1) { - throw new DavException(DavServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); - } - final String byteRange = byteRanges[0]; - final String[] bytePos = StringUtils.splitPreserveAllTokens(byteRange, RANGE_SEP); - if (bytePos.length != 2 || bytePos[0].isEmpty() && bytePos[1].isEmpty()) { - throw new DavException(DavServletResponse.SC_BAD_REQUEST, "malformed range header: " + rangeHeader); - } - return new ImmutablePair<>(bytePos[0], bytePos[1]); - } - - /** - * @return true if a partial response should be generated according to an If-Range precondition. - */ - private boolean isIfRangeHeaderSatisfied(FileLocator file, String ifRangeHeader) throws DavException { - if (ifRangeHeader == null) { - // no header set -> satisfied implicitly - return true; - } else { - try { - Instant expectedTime = Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(ifRangeHeader)); - Instant actualTime = file.lastModified(); - return expectedTime.compareTo(actualTime) == 0; - } catch (DateTimeParseException e) { - throw new DavException(DavServletResponse.SC_BAD_REQUEST, "Unsupported If-Range header: " + ifRangeHeader); - } - } - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/NullInputContext.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/NullInputContext.java deleted file mode 100644 index 7e990a5e3..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/NullInputContext.java +++ /dev/null @@ -1,53 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav.jackrabbitservlet; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; - -import org.apache.jackrabbit.webdav.io.InputContext; - -class NullInputContext implements InputContext { - - @Override - public boolean hasStream() { - return true; - } - - @Override - public InputStream getInputStream() { - return new ByteArrayInputStream(new byte[0]); - } - - @Override - public long getModificationTime() { - return 0; - } - - @Override - public String getContentLanguage() { - return null; - } - - @Override - public long getContentLength() { - return 0; - } - - @Override - public String getContentType() { - return null; - } - - @Override - public String getProperty(String propertyName) { - return null; - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/UncheckedDavException.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/UncheckedDavException.java deleted file mode 100644 index 9a8145ca0..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/UncheckedDavException.java +++ /dev/null @@ -1,30 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav.jackrabbitservlet; - -import org.apache.jackrabbit.webdav.DavException; - -public class UncheckedDavException extends RuntimeException { - - private final int errorCode; - - public UncheckedDavException(int errorCode, String message) { - this(errorCode, message, null); - } - - public UncheckedDavException(int errorCode, String message, Throwable cause) { - super(message, cause); - this.errorCode = errorCode; - } - - public DavException toDavException() { - return new DavException(errorCode, getMessage(), getCause(), null); - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/WebDavServlet.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/WebDavServlet.java deleted file mode 100644 index 9cbc5d5b6..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/WebDavServlet.java +++ /dev/null @@ -1,168 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.frontend.webdav.jackrabbitservlet; - -import java.io.IOException; -import java.net.URI; - -import javax.servlet.ServletException; - -import org.apache.commons.lang3.ArrayUtils; -import org.apache.jackrabbit.webdav.DavException; -import org.apache.jackrabbit.webdav.DavLocatorFactory; -import org.apache.jackrabbit.webdav.DavResource; -import org.apache.jackrabbit.webdav.DavResourceFactory; -import org.apache.jackrabbit.webdav.DavServletResponse; -import org.apache.jackrabbit.webdav.DavSession; -import org.apache.jackrabbit.webdav.DavSessionProvider; -import org.apache.jackrabbit.webdav.WebdavRequest; -import org.apache.jackrabbit.webdav.WebdavResponse; -import org.apache.jackrabbit.webdav.lock.ActiveLock; -import org.apache.jackrabbit.webdav.lock.Scope; -import org.apache.jackrabbit.webdav.lock.Type; -import org.apache.jackrabbit.webdav.server.AbstractWebdavServlet; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.jackrabbit.FileSystemResourceLocatorFactory; -import org.eclipse.jetty.io.EofException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class WebDavServlet extends AbstractWebdavServlet { - - private static final long serialVersionUID = -6632687979352625020L; - - private static final Logger LOG = LoggerFactory.getLogger(WebDavServlet.class); - - private final DavSessionProvider davSessionProvider; - private final DavLocatorFactory davLocatorFactory; - private final DavResourceFactory davResourceFactory; - - public WebDavServlet(URI contextRootUri, Folder root) { - davSessionProvider = new DavSessionProviderImpl(); - davLocatorFactory = new FileSystemResourceLocatorFactory(contextRootUri, root); - davResourceFactory = new FilesystemResourceFactory(); - } - - @Override - protected boolean isPreconditionValid(WebdavRequest request, DavResource resource) { - return !resource.exists() || request.matchesIfHeader(resource); - } - - @Override - public DavSessionProvider getDavSessionProvider() { - return davSessionProvider; - } - - @Override - public void setDavSessionProvider(DavSessionProvider davSessionProvider) { - throw new UnsupportedOperationException("Setting davSessionProvider not supported."); - } - - @Override - public DavLocatorFactory getLocatorFactory() { - return davLocatorFactory; - } - - @Override - public void setLocatorFactory(DavLocatorFactory locatorFactory) { - throw new UnsupportedOperationException("Setting locatorFactory not supported."); - } - - @Override - public DavResourceFactory getResourceFactory() { - return davResourceFactory; - } - - @Override - public void setResourceFactory(DavResourceFactory resourceFactory) { - throw new UnsupportedOperationException("Setting resourceFactory not supported."); - } - - /* Unchecked DAV exception rewrapping */ - - @Override - protected boolean execute(WebdavRequest request, WebdavResponse response, int method, DavResource resource) throws ServletException, IOException, DavException { - try { - return super.execute(request, response, method, resource); - } catch (UncheckedDavException e) { - throw e.toDavException(); - } - } - - /* GET stuff */ - - @Override - protected void doGet(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { - try { - super.doGet(request, response, resource); - } catch (EofException e) { - // Jetty EOF (other than IO EOF) is thrown when the connection is closed by the client. - // If the client is no longer interested in further content, we don't care. - if (LOG.isDebugEnabled()) { - LOG.trace("Unexpected end of stream during GET (client hung up)."); - } - } - } - - /* LOCK stuff */ - - @Override - protected int validateDestination(DavResource destResource, WebdavRequest request, boolean checkHeader) throws DavException { - if (isLocked(destResource) && !hasCorrectLockTokens(request.getDavSession(), destResource)) { - throw new DavException(DavServletResponse.SC_LOCKED, "The destination resource is locked"); - } - return super.validateDestination(destResource, request, checkHeader); - } - - @Override - protected void doPut(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { - if (isLocked(resource) && !hasCorrectLockTokens(request.getDavSession(), resource)) { - throw new DavException(DavServletResponse.SC_LOCKED, "The resource is locked"); - } - super.doPut(request, response, resource); - } - - @Override - protected void doDelete(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { - if (isLocked(resource) && !hasCorrectLockTokens(request.getDavSession(), resource)) { - throw new DavException(DavServletResponse.SC_LOCKED, "The resource is locked"); - } - super.doDelete(request, response, resource); - } - - @Override - protected void doMove(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { - if (isLocked(resource) && !hasCorrectLockTokens(request.getDavSession(), resource)) { - throw new DavException(DavServletResponse.SC_LOCKED, "The source resource is locked"); - } - super.doMove(request, response, resource); - } - - @Override - protected void doPropPatch(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { - if (isLocked(resource) && !hasCorrectLockTokens(request.getDavSession(), resource)) { - throw new DavException(DavServletResponse.SC_LOCKED, "The resource is locked"); - } - super.doPropPatch(request, response, resource); - } - - private boolean hasCorrectLockTokens(DavSession session, DavResource resource) { - boolean access = false; - final String[] providedLockTokens = session.getLockTokens(); - for (ActiveLock lock : resource.getLocks()) { - access |= ArrayUtils.contains(providedLockTokens, lock.getToken()); - } - return access; - } - - private boolean isLocked(DavResource resource) { - return resource.hasLock(Type.WRITE, Scope.EXCLUSIVE) || resource.hasLock(Type.WRITE, Scope.SHARED); - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/AbstractWebDavMount.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/AbstractWebDavMount.java deleted file mode 100644 index 6bc8c9612..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/AbstractWebDavMount.java +++ /dev/null @@ -1,18 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav.mount; - -abstract class AbstractWebDavMount implements WebDavMount { - - @Override - public void close() throws Exception { - this.unmount(); - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/FallbackWebDavMounter.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/FallbackWebDavMounter.java deleted file mode 100644 index a05d19bd1..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/FallbackWebDavMounter.java +++ /dev/null @@ -1,64 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014, 2016 Markus Kreusch - * This file is licensed under the terms of the MIT license. - * See the LICENSE.txt file for more info. - * - * Contributors: - * Markus Kreusch - Refactored WebDavMounter to use strategy pattern - ******************************************************************************/ -package org.cryptomator.frontend.webdav.mount; - -import java.net.URI; -import java.util.Map; -import java.util.Optional; - -import org.cryptomator.frontend.CommandFailedException; -import org.cryptomator.frontend.Frontend.MountParam; - -/** - * A WebDavMounter acting as fallback if no other mounter works. - * - * @author Markus Kreusch - */ -final class FallbackWebDavMounter implements WebDavMounterStrategy { - - @Override - public boolean shouldWork(Map> mountParams) { - return true; - } - - @Override - public void warmUp(int serverPort) { - // no-op - } - - @Override - public WebDavMount mount(URI uri, Map> mountParams) { - displayMountInstructions(); - return new AbstractWebDavMount() { - @Override - public void unmount() { - displayUnmountInstructions(); - } - - @Override - public void reveal() throws CommandFailedException { - displayRevealInstructions(); - } - }; - } - - private void displayMountInstructions() { - // TODO display message to user pointing to cryptomator.org/mounting#mount which describes what to do - // Machine-readable mount instructions: http://tools.ietf.org/html/rfc4709#page-5 :-) - } - - private void displayUnmountInstructions() { - // TODO display message to user pointing to cryptomator.org/mounting#unmount which describes what to do - } - - private void displayRevealInstructions() { - // TODO display message to user pointing to cryptomator.org/mounting#reveal which describes what to do - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/LinuxGvfsDavMounter.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/LinuxGvfsDavMounter.java deleted file mode 100644 index 51834277c..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/LinuxGvfsDavMounter.java +++ /dev/null @@ -1,94 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014, 2016 Sebastian Stenzel, Markus Kreusch - * 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 - * Markus Kreusch - Refactored WebDavMounter to use strategy pattern - * Mohit Raju - Added fallback schema-name "webdav" when opening file managers - ******************************************************************************/ -package org.cryptomator.frontend.webdav.mount; - -import java.net.URI; -import java.util.Map; -import java.util.Optional; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import org.apache.commons.lang3.SystemUtils; -import org.cryptomator.frontend.CommandFailedException; -import org.cryptomator.frontend.Frontend.MountParam; -import org.cryptomator.frontend.webdav.mount.command.Script; - -@Singleton -final class LinuxGvfsDavMounter implements WebDavMounterStrategy { - - @Inject - LinuxGvfsDavMounter() { - } - - @Override - public boolean shouldWork(Map> mountParams) { - if (SystemUtils.IS_OS_LINUX) { - Optional prefScheme = mountParams.getOrDefault(MountParam.PREFERRED_GVFS_SCHEME, Optional.empty()); - boolean prefSchemeIsUnspecifiedOrDav = !prefScheme.isPresent() || prefScheme.get().equalsIgnoreCase("dav"); - final Script checkScripts = Script.fromLines("which gvfs-mount xdg-open"); - try { - checkScripts.execute(); - return prefSchemeIsUnspecifiedOrDav; - } catch (CommandFailedException e) { - return false; - } - } else { - return false; - } - } - - @Override - public void warmUp(int serverPort) { - // no-op - } - - @Override - public WebDavMount mount(URI uri, Map> mountParams) throws CommandFailedException { - final Script mountScript = Script.fromLines("set -x", "gvfs-mount \"dav:$DAV_SSP\"").addEnv("DAV_SSP", uri.getRawSchemeSpecificPart()); - mountScript.execute(); - return new LinuxGvfsDavMount(uri); - } - - private static class LinuxGvfsDavMount extends AbstractWebDavMount { - private final URI webDavUri; - private final Script testMountStillExistsScript; - private final Script unmountScript; - - private LinuxGvfsDavMount(URI webDavUri) { - this.webDavUri = webDavUri; - this.testMountStillExistsScript = Script.fromLines("set -x", "test `gvfs-mount --list | grep \"$DAV_SSP\" | wc -l` -eq 1").addEnv("DAV_SSP", webDavUri.getRawSchemeSpecificPart()); - this.unmountScript = Script.fromLines("set -x", "gvfs-mount -u \"dav:$DAV_SSP\"").addEnv("DAV_SSP", webDavUri.getRawSchemeSpecificPart()); - } - - @Override - public void unmount() throws CommandFailedException { - boolean mountStillExists; - try { - testMountStillExistsScript.execute(); - mountStillExists = true; - } catch (CommandFailedException e) { - mountStillExists = false; - } - // only attempt unmount if user didn't unmount manually: - if (mountStillExists) { - unmountScript.execute(); - } - } - - @Override - public void reveal() throws CommandFailedException { - Script.fromLines("set -x", "gvfs-open \"dav:$DAV_SSP\"").addEnv("DAV_SSP", webDavUri.getRawSchemeSpecificPart()).execute(); - } - - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/LinuxGvfsWebDavMounter.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/LinuxGvfsWebDavMounter.java deleted file mode 100644 index 219709294..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/LinuxGvfsWebDavMounter.java +++ /dev/null @@ -1,94 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014, 2016 Sebastian Stenzel, Markus Kreusch - * 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 - * Markus Kreusch - Refactored WebDavMounter to use strategy pattern - * Mohit Raju - Added fallback schema-name "webdav" when opening file managers - ******************************************************************************/ -package org.cryptomator.frontend.webdav.mount; - -import java.net.URI; -import java.util.Map; -import java.util.Optional; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import org.apache.commons.lang3.SystemUtils; -import org.cryptomator.frontend.CommandFailedException; -import org.cryptomator.frontend.Frontend.MountParam; -import org.cryptomator.frontend.webdav.mount.command.Script; - -@Singleton -final class LinuxGvfsWebDavMounter implements WebDavMounterStrategy { - - @Inject - LinuxGvfsWebDavMounter() { - } - - @Override - public boolean shouldWork(Map> mountParams) { - if (SystemUtils.IS_OS_LINUX) { - Optional prefScheme = mountParams.getOrDefault(MountParam.PREFERRED_GVFS_SCHEME, Optional.empty()); - boolean prefSchemeIsUnspecifiedOrWebDav = !prefScheme.isPresent() || prefScheme.get().equalsIgnoreCase("webdav"); - final Script checkScripts = Script.fromLines("which gvfs-mount xdg-open"); - try { - checkScripts.execute(); - return prefSchemeIsUnspecifiedOrWebDav; - } catch (CommandFailedException e) { - return false; - } - } else { - return false; - } - } - - @Override - public void warmUp(int serverPort) { - // no-op - } - - @Override - public WebDavMount mount(URI uri, Map> mountParams) throws CommandFailedException { - final Script mountScript = Script.fromLines("set -x", "gvfs-mount \"dav:$DAV_SSP\"").addEnv("DAV_SSP", uri.getRawSchemeSpecificPart()); - mountScript.execute(); - return new LinuxGvfsWebDavMount(uri); - } - - private static class LinuxGvfsWebDavMount extends AbstractWebDavMount { - private final URI webDavUri; - private final Script testMountStillExistsScript; - private final Script unmountScript; - - private LinuxGvfsWebDavMount(URI webDavUri) { - this.webDavUri = webDavUri; - this.testMountStillExistsScript = Script.fromLines("set -x", "test `gvfs-mount --list | grep \"$DAV_SSP\" | wc -l` -eq 1").addEnv("DAV_SSP", webDavUri.getRawSchemeSpecificPart()); - this.unmountScript = Script.fromLines("set -x", "gvfs-mount -u \"dav:$DAV_SSP\"").addEnv("DAV_SSP", webDavUri.getRawSchemeSpecificPart()); - } - - @Override - public void unmount() throws CommandFailedException { - boolean mountStillExists; - try { - testMountStillExistsScript.execute(); - mountStillExists = true; - } catch (CommandFailedException e) { - mountStillExists = false; - } - // only attempt unmount if user didn't unmount manually: - if (mountStillExists) { - unmountScript.execute(); - } - } - - @Override - public void reveal() throws CommandFailedException { - Script.fromLines("set -x", "gvfs-open \"webdav:$DAV_SSP\"").addEnv("DAV_SSP", webDavUri.getRawSchemeSpecificPart()).execute(); - } - - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/MacOsXAppleScriptWebDavMounter.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/MacOsXAppleScriptWebDavMounter.java deleted file mode 100644 index 0bd477541..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/MacOsXAppleScriptWebDavMounter.java +++ /dev/null @@ -1,123 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014, 2016 Sebastian Stenzel, Markus Kreusch - * 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, strategy fine tuning - * Markus Kreusch - Refactored WebDavMounter to use strategy pattern - ******************************************************************************/ -package org.cryptomator.frontend.webdav.mount; - -import java.io.IOException; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.util.Comparator; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.TimeUnit; - -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.SystemUtils; -import org.cryptomator.frontend.CommandFailedException; -import org.cryptomator.frontend.Frontend.MountParam; - -@Singleton -final class MacOsXAppleScriptWebDavMounter implements WebDavMounterStrategy { - - private final Comparator semVerComparator; - - @Inject - MacOsXAppleScriptWebDavMounter(@Named("SemVer") Comparator semVerComparator) { - this.semVerComparator = semVerComparator; - } - - @Override - public boolean shouldWork(Map> mountParams) { - return SystemUtils.IS_OS_MAC_OSX && semVerComparator.compare(SystemUtils.OS_VERSION, "10.10") >= 0; - } - - @Override - public void warmUp(int serverPort) { - // no-op - } - - @Override - public WebDavMount mount(URI uri, Map> mountParams) throws CommandFailedException { - try { - String mountAppleScript = String.format("mount volume \"%s\"", uri.toString()); - ProcessBuilder mount = new ProcessBuilder("/usr/bin/osascript", "-e", mountAppleScript); - Process mountProcess = mount.start(); - String stdout = IOUtils.toString(mountProcess.getInputStream(), StandardCharsets.UTF_8); - waitForProcessAndCheckSuccess(mountProcess, 1, TimeUnit.SECONDS); - String volumeIdentifier = StringUtils.trim(StringUtils.removeStart(stdout, "file ")); - String waitAppleScript1 = String.format("tell application \"Finder\" to repeat while not (\"%s\" exists)", volumeIdentifier); - String waitAppleScript2 = "delay 0.1"; - String waitAppleScript3 = "end repeat"; - ProcessBuilder wait = new ProcessBuilder("/usr/bin/osascript", "-e", waitAppleScript1, "-e", waitAppleScript2, "-e", waitAppleScript3); - Process waitProcess = wait.start(); - waitForProcessAndCheckSuccess(waitProcess, 5, TimeUnit.SECONDS); - return new MacWebDavMount(volumeIdentifier); - } catch (IOException e) { - throw new CommandFailedException(e); - } - } - - private static class MacWebDavMount extends AbstractWebDavMount { - private final ProcessBuilder revealCommand; - private final ProcessBuilder unmountCommand; - - private MacWebDavMount(String volumeIdentifier) { - String openAppleScript = String.format("tell application \"Finder\" to open \"%s\"", volumeIdentifier); - String activateAppleScript = String.format("tell application \"Finder\" to activate \"%s\"", volumeIdentifier); - String ejectAppleScript = String.format("tell application \"Finder\" to if \"%s\" exists then eject \"%s\"", volumeIdentifier, volumeIdentifier); - - this.revealCommand = new ProcessBuilder("/usr/bin/osascript", "-e", openAppleScript, "-e", activateAppleScript); - this.unmountCommand = new ProcessBuilder("/usr/bin/osascript", "-e", ejectAppleScript); - } - - @Override - public void unmount() throws CommandFailedException { - try { - Process proc = unmountCommand.start(); - waitForProcessAndCheckSuccess(proc, 1, TimeUnit.SECONDS); - } catch (IOException e) { - throw new CommandFailedException(e); - } - } - - @Override - public void reveal() throws CommandFailedException { - try { - Process proc = revealCommand.start(); - waitForProcessAndCheckSuccess(proc, 2, TimeUnit.SECONDS); - } catch (IOException e) { - throw new CommandFailedException(e); - } - } - - } - - private static void waitForProcessAndCheckSuccess(Process proc, long timeout, TimeUnit unit) throws CommandFailedException, IOException { - try { - boolean finishedInTime = proc.waitFor(timeout, unit); - if (!finishedInTime) { - proc.destroyForcibly(); - throw new CommandFailedException("Command timed out."); - } - int exitCode = proc.exitValue(); - if (exitCode != 0) { - String error = IOUtils.toString(proc.getErrorStream(), StandardCharsets.UTF_8); - throw new CommandFailedException("Command failed with exit code " + exitCode + ". Stderr: " + error); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/MacOsXShellScriptWebDavMounter.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/MacOsXShellScriptWebDavMounter.java deleted file mode 100644 index 8e834821a..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/MacOsXShellScriptWebDavMounter.java +++ /dev/null @@ -1,89 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014, 2016 Sebastian Stenzel, Markus Kreusch - * 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, strategy fine tuning - * Markus Kreusch - Refactored WebDavMounter to use strategy pattern - ******************************************************************************/ -package org.cryptomator.frontend.webdav.mount; - -import java.net.URI; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.util.Comparator; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; - -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; - -import org.apache.commons.lang3.SystemUtils; -import org.cryptomator.frontend.CommandFailedException; -import org.cryptomator.frontend.Frontend.MountParam; -import org.cryptomator.frontend.webdav.mount.command.Script; - -@Singleton -final class MacOsXShellScriptWebDavMounter implements WebDavMounterStrategy { - - private final Comparator semVerComparator; - - @Inject - MacOsXShellScriptWebDavMounter(@Named("SemVer") Comparator semVerComparator) { - this.semVerComparator = semVerComparator; - } - - @Override - public boolean shouldWork(Map> mountParams) { - return SystemUtils.IS_OS_MAC_OSX && semVerComparator.compare(SystemUtils.OS_VERSION, "10.10") < 0; - } - - @Override - public void warmUp(int serverPort) { - // no-op - } - - @Override - public WebDavMount mount(URI uri, Map> mountParams) throws CommandFailedException { - final String mountName = mountParams.getOrDefault(MountParam.MOUNT_NAME, Optional.empty()).orElseThrow(() -> { - return new IllegalArgumentException("Missing mount parameter MOUNT_NAME."); - }); - - // we don't use the uri to derive a path, as it *could* be longer than 255 chars. - final String path = "/Volumes/Cryptomator_" + UUID.randomUUID().toString(); - final Script mountScript = Script.fromLines("mkdir \"$MOUNT_PATH\"", "mount_webdav -S -v $MOUNT_NAME \"$DAV_AUTHORITY$DAV_PATH\" \"$MOUNT_PATH\"").addEnv("DAV_AUTHORITY", uri.getRawAuthority()) - .addEnv("DAV_PATH", uri.getRawPath()).addEnv("MOUNT_PATH", path).addEnv("MOUNT_NAME", mountName); - mountScript.execute(); - return new MacWebDavMount(path); - } - - private static class MacWebDavMount extends AbstractWebDavMount { - private final String mountPath; - private final Script revealScript; - private final Script unmountScript; - - private MacWebDavMount(String mountPath) { - this.mountPath = mountPath; - this.revealScript = Script.fromLines("open \"$MOUNT_PATH\"").addEnv("MOUNT_PATH", mountPath); - this.unmountScript = Script.fromLines("diskutil umount $MOUNT_PATH").addEnv("MOUNT_PATH", mountPath); - } - - @Override - public void unmount() throws CommandFailedException { - // only attempt unmount if user didn't unmount manually: - if (Files.exists(FileSystems.getDefault().getPath(mountPath))) { - unmountScript.execute(); - } - } - - @Override - public void reveal() throws CommandFailedException { - revealScript.execute(); - } - - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WebDavMount.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WebDavMount.java deleted file mode 100644 index cd00f651f..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WebDavMount.java +++ /dev/null @@ -1,34 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014, 2016 Markus Kreusch - * This file is licensed under the terms of the MIT license. - * See the LICENSE.txt file for more info. - * - * Contributors: - * Markus Kreusch - Refactored WebDavMounter to use strategy pattern - ******************************************************************************/ -package org.cryptomator.frontend.webdav.mount; - -import org.cryptomator.frontend.CommandFailedException; - -/** - * A mounted webdav share. - * - * @author Markus Kreusch - */ -public interface WebDavMount extends AutoCloseable { - - /** - * Unmounts this {@code WebDavMount}. - * - * @throws CommandFailedException if the unmount operation fails - */ - void unmount() throws CommandFailedException; - - /** - * Reveals the mounted drive in the operating systems default file browser. - * - * @throws CommandFailedException if the reveal operation fails - */ - void reveal() throws CommandFailedException; - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WebDavMounter.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WebDavMounter.java deleted file mode 100644 index 4c2bace9a..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WebDavMounter.java +++ /dev/null @@ -1,32 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014, 2016 Sebastian Stenzel - * 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 - * Markus Kreusch - Refactored to use strategy pattern - ******************************************************************************/ -package org.cryptomator.frontend.webdav.mount; - -import java.net.URI; -import java.util.Map; -import java.util.Optional; - -import org.cryptomator.frontend.CommandFailedException; -import org.cryptomator.frontend.Frontend.MountParam; - -public interface WebDavMounter { - - /** - * Tries to mount a given webdav share. - * - * @param uri URI of the webdav share - * @param mountParams additional mount parameters, that might not get ignored by some OS-specific mounters. - * @return a {@link WebDavMount} representing the mounted share - * @throws CommandFailedException if the mount operation fails - * @throws IllegalArgumentException if mountParams is missing expected options - */ - WebDavMount mount(URI uri, Map> mountParams) throws CommandFailedException; - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WebDavMounterModule.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WebDavMounterModule.java deleted file mode 100644 index c42151f40..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WebDavMounterModule.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.cryptomator.frontend.webdav.mount; - -import java.util.Set; - -import javax.inject.Named; -import javax.inject.Singleton; - -import com.google.common.collect.Sets; - -import dagger.Module; -import dagger.Provides; -import dagger.multibindings.ElementsIntoSet; - -@Module -public class WebDavMounterModule { - - @Provides - @ElementsIntoSet - static Set provideMounters(LinuxGvfsWebDavMounter linuxWebDavMounter, LinuxGvfsDavMounter linuxDavMounter, MacOsXAppleScriptWebDavMounter osxAppleScriptMounter, - MacOsXShellScriptWebDavMounter osxShellScriptMounter, WindowsWebDavMounter winMounter) { - return Sets.newHashSet(linuxWebDavMounter, linuxDavMounter, osxAppleScriptMounter, osxShellScriptMounter, winMounter); - } - - @Provides - @Singleton - @Named("fallback") - static WebDavMounterStrategy provideFallbackStrategy() { - return new FallbackWebDavMounter(); - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WebDavMounterProvider.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WebDavMounterProvider.java deleted file mode 100644 index a1bdf1745..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WebDavMounterProvider.java +++ /dev/null @@ -1,44 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014, 2016 Sebastian Stenzel - * This file is licensed under the terms of the MIT license. - * See the LICENSE.txt file for more info. - * - * Contributors: - * Markus Kreusch - Refactored to use strategy pattern - * Sebastian Stenzel - Refactored to use Guice provider, added warmup-phase for windows mounts. - ******************************************************************************/ -package org.cryptomator.frontend.webdav.mount; - -import java.util.Collection; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; - -import org.cryptomator.frontend.Frontend.MountParam; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@Singleton -public class WebDavMounterProvider { - - private static final Logger LOG = LoggerFactory.getLogger(WebDavMounterProvider.class); - private final Collection availableStrategies; - private final WebDavMounterStrategy fallbackStrategy; - - @Inject - public WebDavMounterProvider(Set availableStrategies, @Named("fallback") WebDavMounterStrategy fallbackStrategy) { - this.availableStrategies = availableStrategies; - this.fallbackStrategy = fallbackStrategy; - } - - public WebDavMounter chooseMounter(Map> mountParams) { - WebDavMounterStrategy result = availableStrategies.stream().filter(strategy -> strategy.shouldWork(mountParams)).findFirst().orElse(fallbackStrategy); - LOG.info("Using {}", result.getClass().getSimpleName()); - return result; - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WebDavMounterStrategy.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WebDavMounterStrategy.java deleted file mode 100644 index ae5d6b687..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WebDavMounterStrategy.java +++ /dev/null @@ -1,35 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014, 2016 Markus Kreusch - * This file is licensed under the terms of the MIT license. - * See the LICENSE.txt file for more info. - * - * Contributors: - * Markus Kreusch - Refactored WebDavMounter to use strategy pattern - * Sebastian Stenzel - minor strategy fine tuning - ******************************************************************************/ -package org.cryptomator.frontend.webdav.mount; - -import java.util.Map; -import java.util.Optional; - -import org.cryptomator.frontend.Frontend.MountParam; - -/** - * A strategy able to mount a webdav share and display it to the user. - * - * @author Markus Kreusch - */ -interface WebDavMounterStrategy extends WebDavMounter { - - /** - * @return {@code false} if this {@code WebDavMounterStrategy} can not work on the local machine, {@code true} if it could work - */ - boolean shouldWork(Map> mountParams); - - /** - * Invoked when mounting strategy gets chosen. On some operating systems (we don't want to tell names here) mounting might be faster, - * when certain things are prepared before the actual mount attempt. - */ - void warmUp(int serverPort); - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WindowsWebDavMounter.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WindowsWebDavMounter.java deleted file mode 100644 index bea6a5cdd..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WindowsWebDavMounter.java +++ /dev/null @@ -1,184 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014, 2016 Sebastian Stenzel, Markus Kreusch - * 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, strategy fine tuning - * Markus Kreusch - Refactored WebDavMounter to use strategy pattern - ******************************************************************************/ -package org.cryptomator.frontend.webdav.mount; - -import static org.cryptomator.frontend.webdav.mount.command.Script.fromLines; - -import java.io.IOException; -import java.io.InterruptedIOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.CharUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.SystemUtils; -import org.cryptomator.frontend.CommandFailedException; -import org.cryptomator.frontend.Frontend.MountParam; -import org.cryptomator.frontend.webdav.mount.command.CommandResult; -import org.cryptomator.frontend.webdav.mount.command.Script; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A {@link WebDavMounterStrategy} utilizing the "net use" command. - *

- * Tested on Windows 7, 8.1 and 10. - */ -@Singleton -final class WindowsWebDavMounter implements WebDavMounterStrategy { - - private static final Logger LOG = LoggerFactory.getLogger(WindowsWebDavMounter.class); - private static final Pattern WIN_MOUNT_DRIVELETTER_PATTERN = Pattern.compile("\\s*([A-Z]):\\s*"); - private static final Pattern REG_QUERY_PROXY_OVERRIDES_PATTERN = Pattern.compile("\\s*ProxyOverride\\s+REG_SZ\\s+(.*)\\s*"); - private static final String AUTO_ASSIGN_DRIVE_LETTER = "*"; - private static final String LOCALHOST = "localhost"; - private static final int MOUNT_TIMEOUT_SECONDS = 60; - private final WindowsDriveLetters driveLetters; - - @Inject - WindowsWebDavMounter(WindowsDriveLetters driveLetters) { - this.driveLetters = driveLetters; - } - - @Override - public boolean shouldWork(Map> mountParams) { - return SystemUtils.IS_OS_WINDOWS; - } - - @Override - public void warmUp(int serverPort) { - // no-op - } - - @Override - public WebDavMount mount(URI uri, Map> mountParams) throws CommandFailedException { - final String driveLetter = mountParams.getOrDefault(MountParam.WIN_DRIVE_LETTER, Optional.empty()).orElse(AUTO_ASSIGN_DRIVE_LETTER); - if (driveLetters.getOccupiedDriveLetters().contains(CharUtils.toChar(driveLetter))) { - throw new CommandFailedException("Drive letter occupied."); - } - - final String hostname = mountParams.getOrDefault(MountParam.HOSTNAME, Optional.empty()).orElse(LOCALHOST); - try { - final URI adjustedUri = new URI(uri.getScheme(), uri.getUserInfo(), hostname, uri.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment()); - CommandResult mountResult = mount(adjustedUri, driveLetter); - return new WindowsWebDavMount(AUTO_ASSIGN_DRIVE_LETTER.equals(driveLetter) ? getDriveLetter(mountResult.getStdOut()) : driveLetter); - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Invalid host: " + hostname); - } - } - - private CommandResult mount(URI uri, String driveLetter) throws CommandFailedException { - try { - addProxyOverrides(uri); - } catch (IOException e) { - throw new CommandFailedException(e); - } - - final String driveLetterStr = AUTO_ASSIGN_DRIVE_LETTER.equals(driveLetter) ? AUTO_ASSIGN_DRIVE_LETTER : driveLetter + ":"; - final Script mountScript = fromLines("net use %DRIVE_LETTER% \\\\%DAV_HOST%@%DAV_PORT%\\DavWWWRoot%DAV_UNC_PATH% /persistent:no"); - mountScript.addEnv("DRIVE_LETTER", driveLetterStr); - mountScript.addEnv("DAV_HOST", uri.getHost()); - mountScript.addEnv("DAV_PORT", String.valueOf(uri.getPort())); - mountScript.addEnv("DAV_UNC_PATH", uri.getRawPath().replace('/', '\\')); - return mountScript.execute(MOUNT_TIMEOUT_SECONDS, TimeUnit.SECONDS); - } - - private void addProxyOverrides(URI uri) throws IOException, CommandFailedException { - try { - // get existing value for ProxyOverride key from reqistry: - ProcessBuilder query = new ProcessBuilder("reg", "query", "\"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\"", "/v", "ProxyOverride"); - Process queryCmd = query.start(); - String queryStdOut = IOUtils.toString(queryCmd.getInputStream(), StandardCharsets.UTF_8); - int queryResult = queryCmd.waitFor(); - - // determine new value for ProxyOverride key: - Set overrides = new HashSet<>(); - Matcher matcher = REG_QUERY_PROXY_OVERRIDES_PATTERN.matcher(queryStdOut); - if (queryResult == 0 && matcher.find()) { - String[] existingOverrides = StringUtils.split(matcher.group(1), ';'); - overrides.addAll(Arrays.asList(existingOverrides)); - } - overrides.removeIf(s -> s.startsWith(uri.getHost() + ":")); - overrides.add(""); - overrides.add(uri.getHost()); - overrides.add(uri.getHost() + ":" + uri.getPort()); - - // set new value: - String overridesStr = StringUtils.join(overrides, ';'); - ProcessBuilder add = new ProcessBuilder("reg", "add", "\"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\"", "/v", "ProxyOverride", "/d", "\"" + overridesStr + "\"", "/f"); - LOG.debug("Invoking command: " + StringUtils.join(add.command(), ' ')); - Process addCmd = add.start(); - int addResult = addCmd.waitFor(); - if (addResult != 0) { - String addStdErr = IOUtils.toString(addCmd.getErrorStream(), StandardCharsets.UTF_8); - throw new CommandFailedException(addStdErr); - } - } catch (IOException | CommandFailedException e) { - LOG.info("Failed to add proxy overrides", e); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - InterruptedIOException ioException = new InterruptedIOException(); - ioException.initCause(e); - throw ioException; - } - } - - private String getDriveLetter(String result) throws CommandFailedException { - final Matcher matcher = WIN_MOUNT_DRIVELETTER_PATTERN.matcher(result); - if (matcher.find()) { - return matcher.group(1); - } else { - throw new CommandFailedException("Failed to get a drive letter from net use output."); - } - } - - private class WindowsWebDavMount extends AbstractWebDavMount { - private final Character driveLetter; - private final Script openExplorerScript; - private final Script unmountScript; - - private WindowsWebDavMount(String driveLetter) { - this.driveLetter = CharUtils.toCharacterObject(driveLetter); - this.openExplorerScript = fromLines("start explorer.exe " + driveLetter + ":"); - this.unmountScript = fromLines("net use " + driveLetter + ": /delete /no"); - } - - @Override - public void unmount() throws CommandFailedException { - // only attempt unmount if user didn't unmount manually: - if (isVolumeMounted()) { - unmountScript.execute(); - } - } - - @Override - public void reveal() throws CommandFailedException { - openExplorerScript.execute(); - } - - private boolean isVolumeMounted() { - return driveLetters.getOccupiedDriveLetters().contains(driveLetter); - } - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/command/CommandResult.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/command/CommandResult.java deleted file mode 100644 index b2fe5b7dc..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/command/CommandResult.java +++ /dev/null @@ -1,108 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014, 2016 Markus Kreusch - * This file is licensed under the terms of the MIT license. - * See the LICENSE.txt file for more info. - * - * Contributors: - * Markus Kreusch - * Sebastian Stenzel - using Futures, lazy loading for out/err. - ******************************************************************************/ -package org.cryptomator.frontend.webdav.mount.command; - -import static java.lang.String.format; - -import java.io.IOException; - -import org.apache.commons.io.IOUtils; -import org.apache.logging.log4j.util.Strings; -import org.cryptomator.frontend.CommandFailedException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public final class CommandResult { - - private static final Logger LOG = LoggerFactory.getLogger(CommandResult.class); - - private final Process process; - private final String stdout; - private final String stderr; - private final CommandFailedException exception; - - /** - * Constructs a CommandResult from a terminated process and closes all its streams. - * - * @param process An already finished process. - */ - CommandResult(Process process) { - String out = null; - String err = null; - CommandFailedException ex = null; - try { - out = IOUtils.toString(process.getInputStream()); - err = IOUtils.toString(process.getErrorStream()); - } catch (IOException e) { - ex = new CommandFailedException(e); - } finally { - this.process = process; - this.stdout = out; - this.stderr = err; - this.exception = ex; - IOUtils.closeQuietly(process.getInputStream()); - IOUtils.closeQuietly(process.getOutputStream()); - IOUtils.closeQuietly(process.getErrorStream()); - logDebugInfo(); - } - } - - /** - * @return Data written to STDOUT - */ - public String getStdOut() throws CommandFailedException { - assertNoException(); - return stdout; - } - - /** - * @return Data written to STDERR - */ - public String getStdErr() throws CommandFailedException { - assertNoException(); - return stderr; - } - - /** - * @return Exit value of the process - */ - public int getExitValue() { - return process.exitValue(); - } - - private void logDebugInfo() { - if (LOG.isDebugEnabled()) { - if (Strings.isEmpty(stderr) && Strings.isEmpty(stdout)) { - LOG.debug("Command execution finished. Exit code: {}", process.exitValue()); - } else if (Strings.isEmpty(stderr)) { - LOG.debug("Command execution finished. Exit code: {}\nOutput: {}", process.exitValue(), stdout); - } else if (Strings.isEmpty(stdout)) { - LOG.debug("Command execution finished. Exit code: {}\nError: {}", process.exitValue(), stderr); - } else { - LOG.debug("Command execution finished. Exit code: {}\n Output: {}\nError: {}", process.exitValue(), stdout, stderr); - } - } - } - - void assertOk() throws CommandFailedException { - assertNoException(); - int exitValue = getExitValue(); - if (exitValue != 0) { - throw new CommandFailedException(format("Command execution failed. Exit code: %d %n# Output:%n%s %n# Error: %n%s", exitValue, stdout, stderr)); - } - } - - private void assertNoException() throws CommandFailedException { - if (exception != null) { - throw exception; - } - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/command/CommandRunner.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/command/CommandRunner.java deleted file mode 100644 index 8df93e237..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/command/CommandRunner.java +++ /dev/null @@ -1,105 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014, 2016 Markus Kreusch - * This file is licensed under the terms of the MIT license. - * See the LICENSE.txt file for more info. - * - * Contributors: - * Markus Kreusch - * Sebastian Stenzel - Refactoring - ******************************************************************************/ -package org.cryptomator.frontend.webdav.mount.command; - -import static java.lang.String.format; -import static org.apache.commons.lang3.SystemUtils.IS_OS_UNIX; -import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS; - -import java.io.IOException; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.stream.Collectors; - -import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.SystemUtils; -import org.cryptomator.frontend.CommandFailedException; - -/** - *

- * Runs commands using a system compatible CLI. - *

- * To detect the system type {@link SystemUtils} is used. The following CLIs are used by default: - *

    - *
  • {@link #WINDOWS_DEFAULT_CLI} if {@link SystemUtils#IS_OS_WINDOWS} - *
  • {@link #UNIX_DEFAULT_CLI} if {@link SystemUtils#IS_OS_UNIX} - *
- *

- * If the path to the executables differs from the default or the system can not be detected the Java system property - * {@value #CLI_EXECUTABLE_PROPERTY} can be set to define it. - *

- * If a CLI executable can not be determined using these methods operation of {@link CommandRunner} will fail with - * {@link IllegalStateException}s. - * - * @author Markus Kreusch - */ -final class CommandRunner { - - public static final String CLI_EXECUTABLE_PROPERTY = "cryptomator.cli"; - public static final String WINDOWS_DEFAULT_CLI[] = {"cmd", "/C"}; - public static final String UNIX_DEFAULT_CLI[] = {"/bin/sh", "-c"}; - private static final Executor CMD_EXECUTOR = Executors.newCachedThreadPool(); - - /** - * Executes all lines in the given script in the specified order. Stops as soon as the first command fails. - * - * @param script Script containing command lines and environment variables. - * @return Result of the last command, if it exited successfully. - * @throws CommandFailedException If one of the command lines in the given script fails. - */ - static CommandResult execute(Script script, long timeout, TimeUnit unit) throws CommandFailedException { - try { - final List env = script.environment().entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.toList()); - final String[] lines = script.getLines(); - if (lines.length == 0) { - throw new IllegalArgumentException("Invalid script"); - } - CommandResult result = null; - for (final String line : lines) { - final String[] cmds = ArrayUtils.add(determineCli(), line); - final Process proc = Runtime.getRuntime().exec(cmds, env.toArray(new String[0])); - result = run(proc, timeout, unit); - result.assertOk(); - } - assert result != null; - return result; - } catch (IOException e) { - throw new CommandFailedException(e); - } - } - - private static CommandResult run(Process process, long timeout, TimeUnit unit) throws CommandFailedException { - try { - final FutureCommandResult futureCommandResult = new FutureCommandResult(process); - CMD_EXECUTOR.execute(futureCommandResult); - return futureCommandResult.get(timeout, unit); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - throw new CommandFailedException("Waiting time elapsed before command execution finished"); - } - } - - private static String[] determineCli() { - final String cliFromProperty = System.getProperty(CLI_EXECUTABLE_PROPERTY); - if (cliFromProperty != null) { - return cliFromProperty.split(""); - } else if (IS_OS_WINDOWS) { - return WINDOWS_DEFAULT_CLI; - } else if (IS_OS_UNIX) { - return UNIX_DEFAULT_CLI; - } else { - throw new IllegalStateException(format("Failed to determine cli to use. Set Java system property %s to the executable.", CLI_EXECUTABLE_PROPERTY)); - } - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/command/FutureCommandResult.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/command/FutureCommandResult.java deleted file mode 100644 index ce2c79f99..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/command/FutureCommandResult.java +++ /dev/null @@ -1,115 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014, 2016 Markus Kreusch - * This file is licensed under the terms of the MIT license. - * See the LICENSE.txt file for more info. - * - * Contributors: - * Sebastian Stenzel - ******************************************************************************/ -package org.cryptomator.frontend.webdav.mount.command; - -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -import org.cryptomator.frontend.CommandFailedException; - -final class FutureCommandResult implements Future, Runnable { - - private final Process process; - private final AtomicBoolean canceled = new AtomicBoolean(); - private final AtomicBoolean done = new AtomicBoolean(); - private final Lock lock = new ReentrantLock(); - private final Condition doneCondition = lock.newCondition(); - - private CommandFailedException exception; - - FutureCommandResult(Process process) { - this.process = process; - } - - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - if (done.get()) { - return false; - } else if (canceled.compareAndSet(false, true)) { - if (mayInterruptIfRunning) { - process.destroyForcibly(); - } - } - return true; - } - - @Override - public boolean isCancelled() { - return canceled.get(); - } - - private void setDone() { - lock.lock(); - try { - done.set(true); - doneCondition.signalAll(); - } finally { - lock.unlock(); - } - } - - @Override - public boolean isDone() { - return done.get(); - } - - @Override - public CommandResult get() throws InterruptedException, ExecutionException { - lock.lock(); - try { - while (!done.get()) { - doneCondition.await(); - } - } finally { - lock.unlock(); - } - if (exception != null) { - throw new ExecutionException(exception); - } - return new CommandResult(process); - } - - @Override - public CommandResult get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { - lock.lock(); - try { - boolean doneInTime = true; - if (!done.get()) { - doneInTime = doneCondition.await(timeout, unit); - } - if (!doneInTime) { - throw new TimeoutException(); - } - } finally { - lock.unlock(); - } - if (exception != null) { - throw new ExecutionException(exception); - } - return new CommandResult(process); - } - - @Override - public void run() { - try { - process.waitFor(); - } catch (InterruptedException e) { - exception = new CommandFailedException(e); - } finally { - setDone(); - } - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/command/Script.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/command/Script.java deleted file mode 100644 index 3a14e822c..000000000 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/command/Script.java +++ /dev/null @@ -1,65 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014, 2016 Markus Kreusch - * This file is licensed under the terms of the MIT license. - * See the LICENSE.txt file for more info. - * - * Contributors: - * Markus Kreusch - ******************************************************************************/ -package org.cryptomator.frontend.webdav.mount.command; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -import org.cryptomator.frontend.CommandFailedException; - -public final class Script { - - private static final int DEFAULT_TIMEOUT_MILLISECONDS = 3000; - - public static Script fromLines(String... commands) { - return new Script(commands); - } - - private final String[] lines; - private final Map environment = new HashMap<>(); - - private Script(String[] lines) { - this.lines = lines; - setEnv(System.getenv()); - } - - public String[] getLines() { - return lines; - } - - public CommandResult execute() throws CommandFailedException { - return CommandRunner.execute(this, DEFAULT_TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS); - } - - public CommandResult execute(long timeout, TimeUnit unit) throws CommandFailedException { - return CommandRunner.execute(this, timeout, unit); - } - - Map environment() { - return environment; - } - - public Script setEnv(Map environment) { - this.environment.clear(); - addEnv(environment); - return this; - } - - public Script addEnv(Map environment) { - this.environment.putAll(environment); - return this; - } - - public Script addEnv(String name, String value) { - environment.put(name, value); - return this; - } - -} diff --git a/main/frontend-webdav/src/test/java/org/cryptomator/filesystem/jackrabbit/FileSystemResourceLocatorFactoryTest.java b/main/frontend-webdav/src/test/java/org/cryptomator/filesystem/jackrabbit/FileSystemResourceLocatorFactoryTest.java deleted file mode 100644 index 61f094feb..000000000 --- a/main/frontend-webdav/src/test/java/org/cryptomator/filesystem/jackrabbit/FileSystemResourceLocatorFactoryTest.java +++ /dev/null @@ -1,69 +0,0 @@ -/******************************************************************************* - * 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.jackrabbit; - -import java.net.URI; -import java.nio.ByteBuffer; - -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.WritableFile; -import org.cryptomator.filesystem.inmem.InMemoryFileSystem; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -public class FileSystemResourceLocatorFactoryTest { - - private FileSystemResourceLocatorFactory factory; - - @Before - public void setupLocatorFactory() { - final FileSystem fs = new InMemoryFileSystem(); - fs.folder("existingFolder").create(); - try (WritableFile writable = fs.file("existingFile").openWritable()) { - writable.write(ByteBuffer.allocate(0)); - } - factory = new FileSystemResourceLocatorFactory(URI.create("http://localhost/contextroot"), fs); - } - - @Test - public void testCreateFolders() { - FileSystemResourceLocator locator = factory.createResourceLocator(null, null, "/foo/bar/"); - Assert.assertTrue(locator instanceof FolderLocator); - Assert.assertEquals("bar", locator.name()); - Assert.assertEquals("foo", locator.parent().get().name()); - Assert.assertEquals("", locator.parent().get().parent().get().name()); - Assert.assertFalse(locator.parent().get().parent().get().parent().isPresent()); - - locator = factory.createResourceLocator("http://localhost/contextroot", "http://localhost/contextroot/foo/bar/"); - Assert.assertTrue(locator instanceof FolderLocator); - Assert.assertEquals("bar", locator.name()); - Assert.assertEquals("foo", locator.parent().get().name()); - Assert.assertEquals("", locator.parent().get().parent().get().name()); - Assert.assertFalse(locator.parent().get().parent().get().parent().isPresent()); - } - - @Test - public void testCreateFiles() { - FileSystemResourceLocator locator = factory.createResourceLocator(null, null, "/foo/bar"); - Assert.assertTrue(locator instanceof FileLocator); - Assert.assertEquals("bar", locator.name()); - Assert.assertEquals("foo", locator.parent().get().name()); - Assert.assertEquals("", locator.parent().get().parent().get().name()); - Assert.assertFalse(locator.parent().get().parent().get().parent().isPresent()); - - locator = factory.createResourceLocator("http://localhost/contextroot", "http://localhost/contextroot/foo/bar"); - Assert.assertTrue(locator instanceof FileLocator); - Assert.assertEquals("bar", locator.name()); - Assert.assertEquals("foo", locator.parent().get().name()); - Assert.assertEquals("", locator.parent().get().parent().get().name()); - Assert.assertFalse(locator.parent().get().parent().get().parent().isPresent()); - } - -} diff --git a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/DefaultServletTest.java b/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/DefaultServletTest.java deleted file mode 100644 index 5d87a9b2b..000000000 --- a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/DefaultServletTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav; - -import static org.mockito.Mockito.mock; - -import java.io.IOException; - -import javax.servlet.Servlet; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.eclipse.jetty.servlet.ServletHolder; -import org.junit.Assert; -import org.junit.Test; -import org.mockito.Mockito; - - -public class DefaultServletTest { - - private Tarpit tarpit = mock(Tarpit.class); - - private DefaultServlet inTest = new DefaultServlet(tarpit); - - @Test - public void testFactory() throws ServletException { - - ServletHolder[] holders = inTest.createServletContextHandler().getServletHandler().getServlets(); - Assert.assertEquals(1, holders.length); - ServletHolder holder = holders[0]; - - Servlet servlet = holder.getServlet(); - Assert.assertTrue(servlet instanceof DefaultServlet); - } - - @Test - public void testResponse() throws IOException, ServletException { - final DefaultServlet servlet = inTest; - final HttpServletRequest request = Mockito.mock(HttpServletRequest.class); - final HttpServletResponse response = Mockito.mock(HttpServletResponse.class); - - servlet.doOptions(request, response); - - Mockito.verify(response).addHeader("MS-Author-Via", "DAV"); - Mockito.verify(response).addHeader("DAV", "1, 2"); - Mockito.verify(response).setStatus(204); - } - -} diff --git a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/FileSystemWebDavServer.java b/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/FileSystemWebDavServer.java deleted file mode 100644 index d2acc3e6d..000000000 --- a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/FileSystemWebDavServer.java +++ /dev/null @@ -1,52 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Markus Kreusch and others. - * This file is licensed under the terms of the MIT license. - * See the LICENSE.txt file for more info. - * - * Contributors: - * Markus Kreusch - initial API and implementation - *******************************************************************************/ -package org.cryptomator.frontend.webdav; - -import java.net.URI; -import java.util.EnumSet; - -import javax.servlet.DispatcherType; - -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.frontend.webdav.filters.LoggingHttpFilter; -import org.eclipse.jetty.servlet.ServletContextHandler; - -class FileSystemWebDavServer { - - private static final WebDavComponent WEVDAV_COMP = DaggerWebDavComponent.create(); - - private final FileSystem fileSystem; - - public FileSystemWebDavServer(FileSystem fileSystem) { - this.fileSystem = fileSystem; - } - - public void run() { - try { - tryRun(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private void tryRun() throws Exception { - WebDavServer server = WEVDAV_COMP.server(); - server.setPort(8080); - server.start(); - - ServletContextHandler servlet = server.addServlet(fileSystem, URI.create("http://localhost:8080/foo")); - servlet.addFilter(LoggingHttpFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); - servlet.start(); - - System.out.println("Server started. Press any key to stop it..."); - System.in.read(); - server.stop(); - } - -} diff --git a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/InMemoryWebDavServer.java b/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/InMemoryWebDavServer.java deleted file mode 100644 index fdcc1c80c..000000000 --- a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/InMemoryWebDavServer.java +++ /dev/null @@ -1,41 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.frontend.webdav; - -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.crypto.CryptoEngineTestModule; -import org.cryptomator.filesystem.crypto.CryptoFileSystemDelegate; -import org.cryptomator.filesystem.crypto.CryptoFileSystemTestComponent; -import org.cryptomator.filesystem.crypto.DaggerCryptoFileSystemTestComponent; -import org.cryptomator.filesystem.inmem.InMemoryFileSystem; -import org.mockito.Mockito; - -public class InMemoryWebDavServer { - - private static final CryptoFileSystemTestComponent CRYPTO_FS_COMP = DaggerCryptoFileSystemTestComponent.builder().cryptoEngineModule(new CryptoEngineTestModule()).build(); - - public static void main(String[] args) throws Exception { - new FileSystemWebDavServer(cryptoFileSystem()).run(); - } - - private static FileSystem cryptoFileSystem() { - FileSystem shorteningFileSystem = shorteningFileSystem(); - CRYPTO_FS_COMP.cryptoFileSystemFactory().initializeNew(shorteningFileSystem, "asd"); - return CRYPTO_FS_COMP.cryptoFileSystemFactory().unlockExisting(shorteningFileSystem, "asd", Mockito.mock(CryptoFileSystemDelegate.class)); - } - - private static FileSystem shorteningFileSystem() { - return CRYPTO_FS_COMP.shorteningFileSystemFactory().get(inMemoryFileSystem()); - } - - private static FileSystem inMemoryFileSystem() { - return new InMemoryFileSystem(); - } - -} diff --git a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/NioWebDavServer.java b/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/NioWebDavServer.java deleted file mode 100644 index b473bc48d..000000000 --- a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/NioWebDavServer.java +++ /dev/null @@ -1,80 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015, 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.frontend.webdav; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.UncheckedIOException; -import java.nio.charset.Charset; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Optional; - -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.crypto.CryptoEngineTestModule; -import org.cryptomator.filesystem.crypto.CryptoFileSystemDelegate; -import org.cryptomator.filesystem.crypto.CryptoFileSystemTestComponent; -import org.cryptomator.filesystem.crypto.DaggerCryptoFileSystemTestComponent; -import org.cryptomator.filesystem.nio.NioFileSystem; -import org.mockito.Mockito; - -public class NioWebDavServer { - - private static final CryptoFileSystemTestComponent CRYPTO_FS_COMP = DaggerCryptoFileSystemTestComponent.builder().cryptoEngineModule(new CryptoEngineTestModule()).build(); - private static final String PATH_TO_SERVE_PROPERTY = "pathToServe"; - - public static void main(String[] args) throws Exception { - new FileSystemWebDavServer(cryptoFileSystem()).run(); - } - - private static FileSystem cryptoFileSystem() { - FileSystem shorteningFileSystem = shorteningFileSystem(); - CRYPTO_FS_COMP.cryptoFileSystemFactory().initializeNew(shorteningFileSystem, "asd"); - return CRYPTO_FS_COMP.cryptoFileSystemFactory().unlockExisting(shorteningFileSystem, "asd", Mockito.mock(CryptoFileSystemDelegate.class)); - } - - private static FileSystem shorteningFileSystem() { - return CRYPTO_FS_COMP.shorteningFileSystemFactory().get(nioFileSystem()); - } - - private static FileSystem nioFileSystem() { - return NioFileSystem.rootedAt(pathToServe()); - } - - private static Path pathToServe() { - Path path = pathFromSystemProperty().orElseGet(NioWebDavServer::pathEnteredByUser); - if (!Files.isDirectory(path)) { - throw new RuntimeException("Path is not a directory"); - } - System.out.println("Serving " + path); - return path; - } - - private static Optional pathFromSystemProperty() { - String value = System.getProperty(PATH_TO_SERVE_PROPERTY); - if (value == null) { - return Optional.empty(); - } else { - return Optional.of(Paths.get(value)); - } - } - - private static Path pathEnteredByUser() { - BufferedReader in = new BufferedReader(new InputStreamReader(System.in, Charset.defaultCharset())); - System.out.print("Enter absolute path to serve (must be an existing directory): "); - try { - return Paths.get(in.readLine()); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - -} diff --git a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/WebDavComponent.java b/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/WebDavComponent.java deleted file mode 100644 index cbeae7914..000000000 --- a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/WebDavComponent.java +++ /dev/null @@ -1,21 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav; - -import javax.inject.Singleton; - -import dagger.Component; - -@Singleton -@Component(modules = {WebDavModule.class}) -public interface WebDavComponent { - - WebDavServer server(); - -} \ No newline at end of file diff --git a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/WebDavServerTest.java b/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/WebDavServerTest.java deleted file mode 100644 index 0255df9d5..000000000 --- a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/WebDavServerTest.java +++ /dev/null @@ -1,528 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav; - -import static org.hamcrest.collection.IsArrayContaining.hasItemInArray; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Random; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.ForkJoinTask; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Collectors; - -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.xpath.XPathException; - -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.httpclient.Header; -import org.apache.commons.httpclient.HttpClient; -import org.apache.commons.httpclient.HttpException; -import org.apache.commons.httpclient.HttpMethod; -import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; -import org.apache.commons.httpclient.methods.ByteArrayRequestEntity; -import org.apache.commons.httpclient.methods.EntityEnclosingMethod; -import org.apache.commons.httpclient.methods.GetMethod; -import org.apache.commons.httpclient.methods.PutMethod; -import org.apache.commons.io.IOUtils; -import org.apache.jackrabbit.webdav.DavException; -import org.apache.jackrabbit.webdav.MultiStatus; -import org.apache.jackrabbit.webdav.MultiStatusResponse; -import org.apache.jackrabbit.webdav.client.methods.CopyMethod; -import org.apache.jackrabbit.webdav.client.methods.DavMethodBase; -import org.apache.jackrabbit.webdav.client.methods.LockMethod; -import org.apache.jackrabbit.webdav.client.methods.MkColMethod; -import org.apache.jackrabbit.webdav.client.methods.MoveMethod; -import org.apache.jackrabbit.webdav.client.methods.PropFindMethod; -import org.apache.jackrabbit.webdav.lock.LockInfo; -import org.apache.jackrabbit.webdav.lock.Scope; -import org.apache.jackrabbit.webdav.lock.Type; -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.filesystem.WritableFile; -import org.cryptomator.filesystem.inmem.InMemoryFileSystem; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.xml.sax.SAXException; - -public class WebDavServerTest { - - private static final WebDavServer SERVER = DaggerWebDavComponent.create().server(); - private static final Logger LOG = LoggerFactory.getLogger(WebDavServerTest.class); - private String servletRoot; - private FileSystem fs; - private ServletContextHandler servlet; - - @BeforeClass - public static void startServer() throws URISyntaxException { - SERVER.start(); - } - - @AfterClass - public static void stopServer() { - SERVER.stop(); - } - - @Before - public void startServlet() throws Exception { - fs = new InMemoryFileSystem(); - servletRoot = "http://localhost:" + SERVER.getPort() + "/test"; - servlet = SERVER.addServlet(fs, URI.create(servletRoot)); - servlet.start(); - } - - @After - public void stopServlet() throws Exception { - servlet.stop(); - } - - @Test - public void testGet() throws HttpException, IOException { - final HttpClient client = new HttpClient(); - - // write test content: - final byte[] testContent = "hello world".getBytes(); - try (WritableFile w = fs.file("foo.txt").openWritable()) { - w.write(ByteBuffer.wrap(testContent)); - } - - // check get response body: - final HttpMethod getMethod = new GetMethod(servletRoot + "/foo.txt"); - final int statusCode = client.executeMethod(getMethod); - Assert.assertEquals(200, statusCode); - Assert.assertThat(getMethod.getResponseHeaders(), hasItemInArray(new Header("Accept-Ranges", "bytes"))); - Assert.assertTrue(IOUtils.contentEquals(new ByteArrayInputStream(testContent), getMethod.getResponseBodyAsStream())); - getMethod.releaseConnection(); - } - - @Test - public void testMkcol() throws HttpException, IOException { - final HttpClient client = new HttpClient(); - - // create directory: - final HttpMethod mkcolMethod = new MkColMethod(servletRoot + "/foo"); - final int statusCode = client.executeMethod(mkcolMethod); - Assert.assertEquals(201, statusCode); - Assert.assertTrue(fs.folder("foo").exists()); - - mkcolMethod.releaseConnection(); - } - - @Test - public void testPut() throws HttpException, IOException { - final HttpClient client = new HttpClient(); - - // create file: - final byte[] testContent = "hello world".getBytes(); - final EntityEnclosingMethod putMethod = new PutMethod(servletRoot + "/foo.txt"); - putMethod.setRequestEntity(new ByteArrayRequestEntity(testContent)); - final int putResponse = client.executeMethod(putMethod); - Assert.assertEquals(201, putResponse); - Assert.assertTrue(fs.file("foo.txt").exists()); - - // check file contents: - ByteBuffer buf = ByteBuffer.allocate(testContent.length); - try (ReadableFile r = fs.file("foo.txt").openReadable()) { - r.read(buf); - } - Assert.assertArrayEquals(testContent, buf.array()); - - putMethod.releaseConnection(); - } - - /* PROPFIND */ - - @Test - public void testPropfind() throws HttpException, IOException, ParserConfigurationException, SAXException, XPathException, DavException { - final HttpClient client = new HttpClient(); - - fs.folder("folder1").create(); - fs.folder("folder2").create(); - try (WritableFile w = fs.file("file1").openWritable()) { - w.write(ByteBuffer.allocate(0)); - } - - // get directory listing of /: - final DavMethodBase propfindMethod = new PropFindMethod(servletRoot + "/"); - final int statusCode = client.executeMethod(propfindMethod); - Assert.assertEquals(207, statusCode); - - // get hrefs contained in dirlisting response: - MultiStatus ms = propfindMethod.getResponseBodyAsMultiStatus(); - propfindMethod.releaseConnection(); - Collection hrefs = Arrays.asList(ms.getResponses()).stream().map(MultiStatusResponse::getHref).collect(Collectors.toSet()); - - Assert.assertTrue(CollectionUtils.containsAll(hrefs, Arrays.asList(servletRoot + "/folder1/", servletRoot + "/folder2/", servletRoot + "/file1"))); - } - - /* LOCK */ - - @Test - public void testLockExisting() throws IOException, DavException { - final HttpClient client = new HttpClient(); - - // create file: - try (WritableFile writable = fs.file("foo.txt").openWritable()) { - writable.write(ByteBuffer.allocate(0)); - } - - // lock existing file: - LockInfo lockInfo = new LockInfo(Scope.EXCLUSIVE, Type.WRITE, "le me", 3600, false); - final DavMethodBase lockMethod = new LockMethod(servletRoot + "/foo.txt", lockInfo); - final int lockResponse = client.executeMethod(lockMethod); - Assert.assertEquals(200, lockResponse); - - lockMethod.releaseConnection(); - } - - @Test - public void testLockNonExisting() throws IOException, DavException { - final HttpClient client = new HttpClient(); - - // lock nonexisting file: - LockInfo lockInfo = new LockInfo(Scope.EXCLUSIVE, Type.WRITE, "le me", 3600, false); - final DavMethodBase lockMethod = new LockMethod(servletRoot + "/foo.txt", lockInfo); - final int lockResponse = client.executeMethod(lockMethod); - Assert.assertEquals(201, lockResponse); - Assert.assertTrue(fs.file("foo.txt").exists()); - - lockMethod.releaseConnection(); - } - - /* MOVE */ - - @Test - public void testMoveFolder() throws HttpException, IOException, ParserConfigurationException, SAXException, XPathException, DavException { - final HttpClient client = new HttpClient(); - - fs.folder("srcFolder").create(); - - final DavMethodBase moveMethod = new MoveMethod(servletRoot + "/srcFolder", servletRoot + "/dstFolder", false); - client.executeMethod(moveMethod); - Assert.assertTrue(moveMethod.succeeded()); - moveMethod.releaseConnection(); - - Assert.assertFalse(fs.folder("srcFolder").exists()); - Assert.assertTrue(fs.folder("dstFolder").exists()); - } - - @Test - public void testMoveFolderOverwrite() throws HttpException, IOException, ParserConfigurationException, SAXException, XPathException, DavException { - final HttpClient client = new HttpClient(); - - fs.folder("srcFolder").create(); - try (WritableFile w = fs.file("dstFolder").openWritable()) { - w.write(ByteBuffer.allocate(0)); - } - - final DavMethodBase moveMethod = new MoveMethod(servletRoot + "/srcFolder", servletRoot + "/dstFolder", true); - client.executeMethod(moveMethod); - Assert.assertTrue(moveMethod.succeeded()); - moveMethod.releaseConnection(); - - Assert.assertFalse(fs.folder("srcFolder").exists()); - Assert.assertTrue(fs.folder("dstFolder").exists()); - Assert.assertFalse(fs.file("dstFolder").exists()); - } - - @Test - public void testMoveFile() throws HttpException, IOException, ParserConfigurationException, SAXException, XPathException, DavException { - final HttpClient client = new HttpClient(); - - try (WritableFile w = fs.file("srcFile").openWritable()) { - w.write(ByteBuffer.allocate(0)); - } - - final DavMethodBase moveMethod = new MoveMethod(servletRoot + "/srcFile/", servletRoot + "/dstFile/", false); - client.executeMethod(moveMethod); - Assert.assertTrue(moveMethod.succeeded()); - moveMethod.releaseConnection(); - - Assert.assertFalse(fs.file("srcFile").exists()); - Assert.assertTrue(fs.file("dstFile").exists()); - } - - @Test - public void testMoveFileOverwrite() throws HttpException, IOException, ParserConfigurationException, SAXException, XPathException, DavException { - final HttpClient client = new HttpClient(); - - try (WritableFile w = fs.file("srcFile").openWritable()) { - w.write(ByteBuffer.allocate(0)); - } - fs.folder("dstFile").create(); - - final DavMethodBase moveMethod = new MoveMethod(servletRoot + "/srcFile/", servletRoot + "/dstFile/", true); - client.executeMethod(moveMethod); - Assert.assertTrue(moveMethod.succeeded()); - moveMethod.releaseConnection(); - - Assert.assertFalse(fs.file("srcFile").exists()); - Assert.assertTrue(fs.file("dstFile").exists()); - Assert.assertFalse(fs.folder("dstFile").exists()); - } - - /* COPY */ - - @Test - public void testCopyFolder() throws HttpException, IOException, ParserConfigurationException, SAXException, XPathException, DavException { - final HttpClient client = new HttpClient(); - - fs.folder("srcFolder").folder("sub").create(); - try (WritableFile w = fs.folder("srcFolder").file("file").openWritable()) { - w.write(ByteBuffer.allocate(0)); - } - - final DavMethodBase copyMethod = new CopyMethod(servletRoot + "/srcFolder", servletRoot + "/dstFolder", false); - client.executeMethod(copyMethod); - Assert.assertTrue(copyMethod.succeeded()); - copyMethod.releaseConnection(); - - Assert.assertTrue(fs.folder("srcFolder").folder("sub").exists()); - Assert.assertTrue(fs.folder("srcFolder").file("file").exists()); - Assert.assertTrue(fs.folder("dstFolder").folder("sub").exists()); - Assert.assertTrue(fs.folder("dstFolder").file("file").exists()); - } - - @Test - public void testCopyFolderOverwrite() throws HttpException, IOException, ParserConfigurationException, SAXException, XPathException, DavException { - final HttpClient client = new HttpClient(); - - fs.folder("srcFolder").create(); - try (WritableFile w = fs.file("dstFolder").openWritable()) { - w.write(ByteBuffer.allocate(0)); - } - - final DavMethodBase copyMethod = new CopyMethod(servletRoot + "/srcFolder", servletRoot + "/dstFolder/", true); - client.executeMethod(copyMethod); - Assert.assertTrue(copyMethod.succeeded()); - copyMethod.releaseConnection(); - - Assert.assertTrue(fs.folder("srcFolder").exists()); - Assert.assertTrue(fs.folder("dstFolder").exists()); - Assert.assertFalse(fs.file("dstFolder").exists()); - } - - @Test - public void testCopyFile() throws HttpException, IOException, ParserConfigurationException, SAXException, XPathException, DavException { - final HttpClient client = new HttpClient(); - - try (WritableFile w = fs.file("srcFile").openWritable()) { - w.write(ByteBuffer.allocate(0)); - } - - final DavMethodBase copyMethod = new CopyMethod(servletRoot + "/srcFile/", servletRoot + "/dstFile/", false); - client.executeMethod(copyMethod); - Assert.assertTrue(copyMethod.succeeded()); - copyMethod.releaseConnection(); - - Assert.assertTrue(fs.file("srcFile").exists()); - Assert.assertTrue(fs.file("dstFile").exists()); - } - - @Test - public void testCopyFileOverwrite() throws HttpException, IOException, ParserConfigurationException, SAXException, XPathException, DavException { - final HttpClient client = new HttpClient(); - - try (WritableFile w = fs.file("srcFile").openWritable()) { - w.write(ByteBuffer.allocate(0)); - } - fs.folder("dstFile").create(); - - final DavMethodBase copyMethod = new CopyMethod(servletRoot + "/srcFile/", servletRoot + "/dstFile/", true); - client.executeMethod(copyMethod); - Assert.assertTrue(copyMethod.succeeded()); - copyMethod.releaseConnection(); - - Assert.assertTrue(fs.file("srcFile").exists()); - Assert.assertTrue(fs.file("dstFile").exists()); - Assert.assertFalse(fs.folder("dstFile").exists()); - } - - /* Range requests */ - - @Test - public void testGetWithUnsatisfiableRange() throws IOException { - final HttpClient client = new HttpClient(); - - // write test content: - final byte[] testContent = "hello world".getBytes(); - try (WritableFile w = fs.file("foo.txt").openWritable()) { - w.write(ByteBuffer.wrap(testContent)); - } - - // check get response body: - final HttpMethod getMethod = new GetMethod(servletRoot + "/foo.txt"); - getMethod.addRequestHeader("Range", "chunks=1-2"); - final int statusCode = client.executeMethod(getMethod); - Assert.assertEquals(416, statusCode); - Assert.assertArrayEquals(testContent, getMethod.getResponseBody()); - getMethod.releaseConnection(); - } - - @Test - public void testMultipleGetWithRangeAsync() throws IOException, URISyntaxException, InterruptedException { - final String testResourceUrl = servletRoot + "/foo.txt"; - - // prepare 8MiB test data: - final byte[] plaintextData = new byte[2097152 * Integer.BYTES]; - final ByteBuffer plaintextDataByteBuffer = ByteBuffer.wrap(plaintextData); - for (int i = 0; i < 2097152; i++) { - plaintextDataByteBuffer.putInt(i); - } - try (WritableFile w = fs.file("foo.txt").openWritable()) { - plaintextDataByteBuffer.flip(); - w.write(plaintextDataByteBuffer); - } - - final MultiThreadedHttpConnectionManager cm = new MultiThreadedHttpConnectionManager(); - cm.getParams().setDefaultMaxConnectionsPerHost(50); - final HttpClient client = new HttpClient(cm); - - // multiple async range requests: - final List> tasks = new ArrayList<>(); - final Random generator = new Random(System.currentTimeMillis()); - - final AtomicBoolean success = new AtomicBoolean(true); - - // 10 full interrupted requests: - for (int i = 0; i < 10; i++) { - final ForkJoinTask task = ForkJoinTask.adapt(() -> { - try { - final HttpMethod getMethod = new GetMethod(testResourceUrl); - final int statusCode = client.executeMethod(getMethod); - if (statusCode != 200) { - LOG.error("Invalid status code for interrupted full request"); - success.set(false); - } - getMethod.getResponseBodyAsStream().read(); - getMethod.getResponseBodyAsStream().close(); - getMethod.releaseConnection(); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - tasks.add(task); - } - - // 50 crappy interrupted range requests: - for (int i = 0; i < 50; i++) { - final int lower = generator.nextInt(plaintextData.length); - final ForkJoinTask task = ForkJoinTask.adapt(() -> { - try { - final HttpMethod getMethod = new GetMethod(testResourceUrl); - getMethod.addRequestHeader("Range", "bytes=" + lower + "-"); - final int statusCode = client.executeMethod(getMethod); - if (statusCode != 206) { - LOG.error("Invalid status code for interrupted range request"); - success.set(false); - } - getMethod.getResponseBodyAsStream().read(); - getMethod.getResponseBodyAsStream().close(); - getMethod.releaseConnection(); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - tasks.add(task); - } - - // 50 normal open range requests: - for (int i = 0; i < 50; i++) { - final int lower = generator.nextInt(plaintextData.length - 512); - final int upper = plaintextData.length - 1; - final ForkJoinTask task = ForkJoinTask.adapt(() -> { - try { - final HttpMethod getMethod = new GetMethod(testResourceUrl); - getMethod.addRequestHeader("Range", "bytes=" + lower + "-"); - final byte[] expected = Arrays.copyOfRange(plaintextData, lower, upper + 1); - final int statusCode = client.executeMethod(getMethod); - final byte[] responseBody = new byte[upper - lower + 10]; - final int bytesRead = IOUtils.read(getMethod.getResponseBodyAsStream(), responseBody); - getMethod.releaseConnection(); - if (statusCode != 206) { - LOG.error("Invalid status code for open range request"); - success.set(false); - } else if (upper - lower + 1 != bytesRead) { - LOG.error("Invalid response length for open range request"); - success.set(false); - } else if (!Arrays.equals(expected, Arrays.copyOfRange(responseBody, 0, bytesRead))) { - LOG.error("Invalid response body for open range request"); - success.set(false); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - tasks.add(task); - } - - // 200 normal closed range requests: - for (int i = 0; i < 200; i++) { - final int pos1 = generator.nextInt(plaintextData.length - 512); - final int pos2 = pos1 + 512; - final ForkJoinTask task = ForkJoinTask.adapt(() -> { - try { - final int lower = Math.min(pos1, pos2); - final int upper = Math.max(pos1, pos2); - final HttpMethod getMethod = new GetMethod(testResourceUrl); - getMethod.addRequestHeader("Range", "bytes=" + lower + "-" + upper); - final byte[] expected = Arrays.copyOfRange(plaintextData, lower, upper + 1); - final int statusCode = client.executeMethod(getMethod); - final byte[] responseBody = new byte[upper - lower + 1]; - final int bytesRead = IOUtils.read(getMethod.getResponseBodyAsStream(), responseBody); - getMethod.releaseConnection(); - if (statusCode != 206) { - LOG.error("Invalid status code for closed range request"); - success.set(false); - } else if (upper - lower + 1 != bytesRead) { - LOG.error("Invalid response length for closed range request"); - success.set(false); - } else if (!Arrays.equals(expected, Arrays.copyOfRange(responseBody, 0, bytesRead))) { - LOG.error("Invalid response body for closed range request"); - success.set(false); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - tasks.add(task); - } - - Collections.shuffle(tasks, generator); - - final ForkJoinPool pool = new ForkJoinPool(4); - for (ForkJoinTask task : tasks) { - pool.execute(task); - } - for (ForkJoinTask task : tasks) { - task.join(); - } - pool.shutdown(); - cm.shutdown(); - - Assert.assertTrue(success.get()); - } - -} diff --git a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/LoggingHttpFilter.java b/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/LoggingHttpFilter.java deleted file mode 100644 index d9443d796..000000000 --- a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/LoggingHttpFilter.java +++ /dev/null @@ -1,182 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav.filters; - -import static java.lang.String.format; -import static java.util.Arrays.asList; - -import java.io.IOException; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; - -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.cryptomator.frontend.webdav.filters.HttpFilter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class LoggingHttpFilter implements HttpFilter { - - private static final Set METHODS_TO_LOG_DETAILED = methodsToLog(); - - private static final Set methodsToLog() { - String methodsToLog = System.getProperty("cryptomator.LoggingHttpFilter.methodsToLogDetailed"); - if (methodsToLog == null) { - return Collections.emptySet(); - } else { - return new HashSet<>(asList(methodsToLog.toUpperCase().split(","))); - } - } - - private final Logger LOG = LoggerFactory.getLogger(LoggingHttpFilter.class); - - @Override - public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { - if (METHODS_TO_LOG_DETAILED.contains(request.getMethod().toUpperCase())) { - logDetailed(request, response, chain); - } else { - logBasic(request, response, chain); - } - } - - private void logBasic(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { - Optional thrown = Optional.empty(); - try { - chain.doFilter(request, response); - } catch (IOException | ServletException e) { - thrown = Optional.of(e); - throw e; - } catch (RuntimeException | Error e) { - thrown = Optional.of(e); - throw e; - } finally { - if (thrown.isPresent()) { - logError(request, thrown.get()); - } else { - logSuccess(request, response); - } - } - } - - private void logDetailed(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { - RecordingHttpServletRequest recordingRequest = new RecordingHttpServletRequest(request); - RecordingHttpServletResponse recordingResponse = new RecordingHttpServletResponse(response); - Optional thrown = Optional.empty(); - try { - chain.doFilter(recordingRequest, recordingResponse); - } catch (IOException | ServletException e) { - thrown = Optional.of(e); - throw e; - } catch (RuntimeException | Error e) { - thrown = Optional.of(e); - throw e; - } finally { - if (thrown.isPresent()) { - logError(recordingRequest, thrown.get()); - } else { - logSuccess(recordingRequest, recordingResponse); - } - } - } - - private void logSuccess(HttpServletRequest request, HttpServletResponse response) { - LOG.debug(format( - "## Request ##\n" + // - "%s %s %s\n" // - + "%s\n" // - + "## Response ##\n" // - + "%s %s\n" // - + "%s\n", // - request.getMethod(), request.getRequestURI(), request.getProtocol(), // - headers(request), // - request.getProtocol(), response.getStatus(), // - headers(response))); - } - - private void logError(HttpServletRequest request, Throwable throwable) { - LOG.error( - format("## Request ##\n" + // - "%s %s %s\n" // - + "%s\n", // - request.getMethod(), request.getRequestURI(), request.getProtocol(), // - headers(request)), // - throwable); - } - - private void logSuccess(RecordingHttpServletRequest request, RecordingHttpServletResponse response) { - LOG.debug(format( - "## Request ##\n" + // - "%s %s %s\n" // - + "%s\n" // - + "%s\n\n" // - + "## Response ##\n" // - + "%s %s\n" // - + "%s\n" // - + "%s", // - request.getMethod(), request.getRequestURI(), request.getProtocol(), // - headers(request), // - new String(request.getRecording()), // - request.getProtocol(), response.getStatus(), // - headers(response), // - new String(response.getRecording()))); - } - - private void logError(RecordingHttpServletRequest request, Throwable throwable) { - LOG.error( - format("## Request ##\n" + // - "%s %s %s\n" // - + "%s\n" // - + "%s\n\n", // - request.getMethod(), request.getRequestURI(), request.getProtocol(), // - headers(request), // - new String(request.getRecording())), // - throwable); - } - - private String headers(HttpServletResponse response) { - StringBuilder result = new StringBuilder(); - for (String headerName : response.getHeaderNames()) { - for (String value : response.getHeaders(headerName)) { - result.append(headerName).append(": ").append(value).append('\n'); - } - } - return result.toString(); - } - - private String headers(HttpServletRequest request) { - StringBuilder result = new StringBuilder(); - Enumeration headerNames = request.getHeaderNames(); - while (headerNames.hasMoreElements()) { - String headerName = headerNames.nextElement(); - Enumeration values = request.getHeaders(headerName); - while (values.hasMoreElements()) { - result.append(headerName).append(": ").append(values.nextElement()).append('\n'); - } - } - return result.toString(); - } - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - // empty - } - - @Override - public void destroy() { - // empty - } - -} diff --git a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/LoopbackFilterTest.java b/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/LoopbackFilterTest.java deleted file mode 100644 index 03dd931b5..000000000 --- a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/LoopbackFilterTest.java +++ /dev/null @@ -1,65 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav.filters; - -import java.io.IOException; -import java.net.InetAddress; -import java.util.Arrays; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.junit.Assume; -import org.junit.Before; -import org.junit.experimental.theories.DataPoints; -import org.junit.experimental.theories.Theories; -import org.junit.experimental.theories.Theory; -import org.junit.runner.RunWith; -import org.mockito.Mockito; - -@RunWith(Theories.class) -public class LoopbackFilterTest { - - @DataPoints - public static final Iterable HOST_NAMES = Arrays.asList("127.0.0.1", "0::1", "1.2.3.4", "google.com"); - - private LoopbackFilter filter; - private FilterChain chain; - private HttpServletRequest request; - private HttpServletResponse response; - - @Before - public void setup() { - filter = new LoopbackFilter(); - chain = Mockito.mock(FilterChain.class); - request = Mockito.mock(HttpServletRequest.class); - response = Mockito.mock(HttpServletResponse.class); - } - - @Theory - public void testWithLoopbackAddress(String hostname) throws IOException, ServletException { - Assume.assumeTrue(InetAddress.getByName(hostname).isLoopbackAddress()); - Mockito.when(request.getRemoteAddr()).thenReturn(hostname); - - filter.doFilter(request, response, chain); - Mockito.verify(chain).doFilter(request, response); - } - - @Theory - public void testWithExternalAddress(String hostname) throws IOException, ServletException { - Assume.assumeFalse(InetAddress.getByName(hostname).isLoopbackAddress()); - Mockito.when(request.getRemoteAddr()).thenReturn(hostname); - - filter.doFilter(request, response, chain); - Mockito.verify(response).sendError(Mockito.eq(405), Mockito.anyString()); - } - -} diff --git a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/MacChunkedPutCompatibilityFilterTest.java b/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/MacChunkedPutCompatibilityFilterTest.java deleted file mode 100644 index b7de53243..000000000 --- a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/MacChunkedPutCompatibilityFilterTest.java +++ /dev/null @@ -1,131 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav.filters; - -import java.io.IOException; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletInputStream; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.cryptomator.frontend.webdav.filters.MacChunkedPutCompatibilityFilter; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; - -public class MacChunkedPutCompatibilityFilterTest { - - private MacChunkedPutCompatibilityFilter filter; - private FilterChain chain; - private HttpServletRequest request; - private HttpServletResponse response; - - @Before - public void setup() { - filter = new MacChunkedPutCompatibilityFilter(); - chain = Mockito.mock(FilterChain.class); - request = Mockito.mock(HttpServletRequest.class); - response = Mockito.mock(HttpServletResponse.class); - } - - @Test - public void testUnfilteredGetRequest() throws IOException, ServletException { - Mockito.when(request.getMethod()).thenReturn("GET"); - filter.doFilter(request, response, chain); - - ArgumentCaptor wrappedReq = ArgumentCaptor.forClass(HttpServletRequest.class); - Mockito.verify(chain).doFilter(wrappedReq.capture(), Mockito.any(ServletResponse.class)); - Assert.assertSame(request, wrappedReq.getValue()); - } - - @Test - public void testUnfilteredPutRequest1() throws IOException, ServletException { - Mockito.when(request.getMethod()).thenReturn("PUT"); - Mockito.when(request.getHeader("Transfer-Encoding")).thenReturn(null); - filter.doFilter(request, response, chain); - - ArgumentCaptor wrappedReq = ArgumentCaptor.forClass(HttpServletRequest.class); - Mockito.verify(chain).doFilter(wrappedReq.capture(), Mockito.any(ServletResponse.class)); - Assert.assertSame(request, wrappedReq.getValue()); - } - - @Test - public void testUnfilteredPutRequest2() throws IOException, ServletException { - Mockito.when(request.getMethod()).thenReturn("PUT"); - Mockito.when(request.getHeader("Transfer-Encoding")).thenReturn("chunked"); - Mockito.when(request.getHeader("X-Expected-Entity-Length")).thenReturn(null); - filter.doFilter(request, response, chain); - - ArgumentCaptor wrappedReq = ArgumentCaptor.forClass(HttpServletRequest.class); - Mockito.verify(chain).doFilter(wrappedReq.capture(), Mockito.any(ServletResponse.class)); - Assert.assertSame(request, wrappedReq.getValue()); - } - - @Test - public void testMalformedXExpectedEntityLengthHeader() throws IOException, ServletException { - Mockito.when(request.getMethod()).thenReturn("PUT"); - Mockito.when(request.getHeader("Transfer-Encoding")).thenReturn("chunked"); - Mockito.when(request.getHeader("X-Expected-Entity-Length")).thenReturn("NaN"); - filter.doFilter(request, response, chain); - - Mockito.verify(response).sendError(Mockito.eq(HttpServletResponse.SC_BAD_REQUEST), Mockito.anyString()); - Mockito.verifyNoMoreInteractions(chain); - } - - /* actual input stream testing */ - - @Test - public void testBoundedInputStream() throws IOException, ServletException { - ServletInputStream in = Mockito.mock(ServletInputStream.class); - - Mockito.when(request.getMethod()).thenReturn("PUT"); - Mockito.when(request.getHeader("Transfer-Encoding")).thenReturn("chunked"); - Mockito.when(request.getHeader("X-Expected-Entity-Length")).thenReturn("5"); - Mockito.when(request.getInputStream()).thenReturn(in); - filter.doFilter(request, response, chain); - - ArgumentCaptor wrappedReq = ArgumentCaptor.forClass(HttpServletRequest.class); - Mockito.verify(chain).doFilter(wrappedReq.capture(), Mockito.any(ServletResponse.class)); - ServletInputStream wrappedIn = wrappedReq.getValue().getInputStream(); - - Mockito.when(in.isFinished()).thenReturn(false); - Assert.assertFalse(wrappedIn.isFinished()); - - Mockito.when(in.isReady()).thenReturn(true); - Assert.assertTrue(wrappedIn.isReady()); - - Mockito.when(in.read()).thenReturn(0xFF); - Assert.assertEquals(0xFF, wrappedIn.read()); - - Mockito.when(in.available()).thenReturn(100); - Assert.assertEquals(100, wrappedIn.available()); - - Mockito.when(in.skip(2)).thenReturn(2l); - Assert.assertEquals(2, wrappedIn.skip(2)); - - Mockito.when(in.read(Mockito.any(), Mockito.eq(0), Mockito.eq(100))).thenReturn(100); - Mockito.when(in.read(Mockito.any(), Mockito.eq(0), Mockito.eq(2))).thenReturn(2); - Assert.assertEquals(2, wrappedIn.read(new byte[100], 0, 100)); - - Mockito.when(in.read()).thenReturn(0xFF); - Assert.assertEquals(-1, wrappedIn.read()); - - Mockito.when(in.isFinished()).thenReturn(false); - Assert.assertTrue(wrappedIn.isFinished()); - - Mockito.when(in.isReady()).thenReturn(true); - Assert.assertFalse(wrappedIn.isReady()); - } - -} diff --git a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/RecordingHttpServletRequest.java b/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/RecordingHttpServletRequest.java deleted file mode 100644 index 271395180..000000000 --- a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/RecordingHttpServletRequest.java +++ /dev/null @@ -1,35 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav.filters; - -import java.io.IOException; - -import javax.servlet.ServletInputStream; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletRequestWrapper; - -class RecordingHttpServletRequest extends HttpServletRequestWrapper { - - private final RecordingServletInputStream recording; - - public RecordingHttpServletRequest(HttpServletRequest request) throws IOException { - super(request); - recording = new RecordingServletInputStream(request.getInputStream()); - } - - @Override - public ServletInputStream getInputStream() throws IOException { - return recording; - } - - public byte[] getRecording() { - return recording.getRecording(); - } - -} diff --git a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/RecordingHttpServletResponse.java b/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/RecordingHttpServletResponse.java deleted file mode 100644 index 6cc264884..000000000 --- a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/RecordingHttpServletResponse.java +++ /dev/null @@ -1,35 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav.filters; - -import java.io.IOException; - -import javax.servlet.ServletOutputStream; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpServletResponseWrapper; - -class RecordingHttpServletResponse extends HttpServletResponseWrapper { - - private final RecordingServletOutputStream recording; - - public RecordingHttpServletResponse(HttpServletResponse response) throws IOException { - super(response); - recording = new RecordingServletOutputStream(response.getOutputStream()); - } - - @Override - public ServletOutputStream getOutputStream() throws IOException { - return recording; - } - - public byte[] getRecording() { - return recording.getRecording(); - } - -} diff --git a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/RecordingServletInputStream.java b/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/RecordingServletInputStream.java deleted file mode 100644 index 791d80eea..000000000 --- a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/RecordingServletInputStream.java +++ /dev/null @@ -1,81 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav.filters; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -import javax.servlet.ReadListener; -import javax.servlet.ServletInputStream; - -import org.apache.commons.io.input.TeeInputStream; - -class RecordingServletInputStream extends ServletInputStream { - - private final ServletInputStream delegate; - private final TeeInputStream teeInputStream; - private final ByteArrayOutputStream recording = new ByteArrayOutputStream(4096); - - public RecordingServletInputStream(ServletInputStream delegate) { - this.delegate = delegate; - this.teeInputStream = new TeeInputStream(delegate, recording); - } - - public int read() throws IOException { - return teeInputStream.read(); - } - - public int read(byte[] b) throws IOException { - return teeInputStream.read(b); - } - - public int read(byte[] b, int off, int len) throws IOException { - return teeInputStream.read(b, off, len); - } - - public boolean isFinished() { - return delegate.isFinished(); - } - - public boolean isReady() { - return delegate.isReady(); - } - - public void setReadListener(ReadListener readListener) { - delegate.setReadListener(readListener); - } - - public long skip(long n) throws IOException { - return teeInputStream.skip(n); - } - - public int available() throws IOException { - return teeInputStream.available(); - } - - public void close() throws IOException { - teeInputStream.close(); - } - - public byte[] getRecording() { - return recording.toByteArray(); - } - - public void mark(int readlimit) { - } - - public void reset() throws IOException { - throw new IOException("Mark not supported"); - } - - public boolean markSupported() { - return false; - } - -} diff --git a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/RecordingServletOutputStream.java b/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/RecordingServletOutputStream.java deleted file mode 100644 index 378384dcf..000000000 --- a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/RecordingServletOutputStream.java +++ /dev/null @@ -1,62 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav.filters; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -import javax.servlet.ServletOutputStream; -import javax.servlet.WriteListener; - -import org.apache.commons.io.output.TeeOutputStream; - -class RecordingServletOutputStream extends ServletOutputStream { - - private final ServletOutputStream delegate; - private final TeeOutputStream teeOutputStream; - private final ByteArrayOutputStream recording = new ByteArrayOutputStream(4096); - - public RecordingServletOutputStream(ServletOutputStream delegate) { - this.delegate = delegate; - this.teeOutputStream = new TeeOutputStream(delegate, recording); - } - - public void write(int b) throws IOException { - teeOutputStream.write(b); - } - - public void write(byte[] b) throws IOException { - teeOutputStream.write(b); - } - - public void write(byte[] b, int off, int len) throws IOException { - teeOutputStream.write(b, off, len); - } - - public void flush() throws IOException { - teeOutputStream.flush(); - } - - public void close() throws IOException { - teeOutputStream.close(); - } - - public boolean isReady() { - return delegate.isReady(); - } - - public void setWriteListener(WriteListener writeListener) { - delegate.setWriteListener(writeListener); - } - - public byte[] getRecording() { - return recording.toByteArray(); - } - -} diff --git a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/UriNormalizationFilterTest.java b/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/UriNormalizationFilterTest.java deleted file mode 100644 index 583839432..000000000 --- a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/filters/UriNormalizationFilterTest.java +++ /dev/null @@ -1,201 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav.filters; - -import java.io.IOException; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.cryptomator.frontend.webdav.filters.UriNormalizationFilter; -import org.cryptomator.frontend.webdav.filters.UriNormalizationFilter.ResourceTypeChecker; -import org.cryptomator.frontend.webdav.filters.UriNormalizationFilter.ResourceTypeChecker.ResourceType; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; - -public class UriNormalizationFilterTest { - - private ResourceTypeChecker resourceTypeChecker; - private UriNormalizationFilter filter; - private FilterChain chain; - private HttpServletRequest request; - private HttpServletResponse response; - - @Before - public void setup() { - resourceTypeChecker = Mockito.mock(ResourceTypeChecker.class); - filter = new UriNormalizationFilter(resourceTypeChecker); - chain = Mockito.mock(FilterChain.class); - request = Mockito.mock(HttpServletRequest.class); - response = Mockito.mock(HttpServletResponse.class); - - Mockito.when(resourceTypeChecker.typeOfResource("/file")).thenReturn(ResourceType.FILE); - Mockito.when(resourceTypeChecker.typeOfResource("/file/")).thenReturn(ResourceType.FILE); - Mockito.when(resourceTypeChecker.typeOfResource("/folder")).thenReturn(ResourceType.FOLDER); - Mockito.when(resourceTypeChecker.typeOfResource("/folder/")).thenReturn(ResourceType.FOLDER); - Mockito.when(resourceTypeChecker.typeOfResource("/404")).thenReturn(ResourceType.UNKNOWN); - Mockito.when(resourceTypeChecker.typeOfResource("/404/")).thenReturn(ResourceType.UNKNOWN); - } - - /* FILE */ - - @Test - public void testFileRequest1() throws IOException, ServletException { - Mockito.when(request.getPathInfo()).thenReturn("/file"); - Mockito.when(request.getRequestURI()).thenReturn("/file"); - filter.doFilter(request, response, chain); - Mockito.verify(resourceTypeChecker).typeOfResource("/file"); - - ArgumentCaptor wrappedReq = ArgumentCaptor.forClass(HttpServletRequest.class); - Mockito.verify(chain).doFilter(wrappedReq.capture(), Mockito.any(ServletResponse.class)); - Assert.assertEquals("/file", wrappedReq.getValue().getRequestURI()); - } - - @Test - public void testFileRequest2() throws IOException, ServletException { - Mockito.when(request.getPathInfo()).thenReturn("/file/"); - Mockito.when(request.getRequestURI()).thenReturn("/file/"); - filter.doFilter(request, response, chain); - Mockito.verify(resourceTypeChecker).typeOfResource("/file/"); - - ArgumentCaptor wrappedReq = ArgumentCaptor.forClass(HttpServletRequest.class); - Mockito.verify(chain).doFilter(wrappedReq.capture(), Mockito.any(ServletResponse.class)); - Assert.assertEquals("/file", wrappedReq.getValue().getRequestURI()); - } - - @Test - public void testCopyFileRequest() throws IOException, ServletException { - Mockito.when(request.getPathInfo()).thenReturn("/file/"); - Mockito.when(request.getRequestURI()).thenReturn("/file/"); - Mockito.when(request.getMethod()).thenReturn("COPY"); - Mockito.when(request.getHeader("Destination")).thenReturn("/404/"); - filter.doFilter(request, response, chain); - - ArgumentCaptor wrappedReq = ArgumentCaptor.forClass(HttpServletRequest.class); - Mockito.verify(chain).doFilter(wrappedReq.capture(), Mockito.any(ServletResponse.class)); - Assert.assertEquals("/404", wrappedReq.getValue().getHeader("Destination")); - } - - /* FOLDER */ - - @Test - public void testFolderRequest1() throws IOException, ServletException { - Mockito.when(request.getPathInfo()).thenReturn("/folder"); - Mockito.when(request.getRequestURI()).thenReturn("/folder"); - filter.doFilter(request, response, chain); - Mockito.verify(resourceTypeChecker).typeOfResource("/folder"); - - ArgumentCaptor wrappedReq = ArgumentCaptor.forClass(HttpServletRequest.class); - Mockito.verify(chain).doFilter(wrappedReq.capture(), Mockito.any(ServletResponse.class)); - Assert.assertEquals("/folder/", wrappedReq.getValue().getRequestURI()); - } - - @Test - public void testFolderRequest2() throws IOException, ServletException { - Mockito.when(request.getPathInfo()).thenReturn("/folder/"); - Mockito.when(request.getRequestURI()).thenReturn("/folder/"); - filter.doFilter(request, response, chain); - Mockito.verify(resourceTypeChecker).typeOfResource("/folder/"); - - ArgumentCaptor wrappedReq = ArgumentCaptor.forClass(HttpServletRequest.class); - Mockito.verify(chain).doFilter(wrappedReq.capture(), Mockito.any(ServletResponse.class)); - Assert.assertEquals("/folder/", wrappedReq.getValue().getRequestURI()); - } - - @Test - public void testMoveFolderRequest() throws IOException, ServletException { - Mockito.when(request.getPathInfo()).thenReturn("/folder"); - Mockito.when(request.getRequestURI()).thenReturn("/folder"); - Mockito.when(request.getMethod()).thenReturn("MOVE"); - Mockito.when(request.getHeader("Destination")).thenReturn("/404"); - filter.doFilter(request, response, chain); - - ArgumentCaptor wrappedReq = ArgumentCaptor.forClass(HttpServletRequest.class); - Mockito.verify(chain).doFilter(wrappedReq.capture(), Mockito.any(ServletResponse.class)); - Assert.assertEquals("/404/", wrappedReq.getValue().getHeader("Destination")); - } - - /* MIXED */ - - @Test - public void testCopyFileToFolderRequest() throws IOException, ServletException { - Mockito.when(request.getPathInfo()).thenReturn("/file/"); - Mockito.when(request.getRequestURI()).thenReturn("/file/"); - Mockito.when(request.getMethod()).thenReturn("COPY"); - Mockito.when(request.getHeader("Destination")).thenReturn("/folder"); - filter.doFilter(request, response, chain); - - ArgumentCaptor wrappedReq = ArgumentCaptor.forClass(HttpServletRequest.class); - Mockito.verify(chain).doFilter(wrappedReq.capture(), Mockito.any(ServletResponse.class)); - Assert.assertEquals("/file", wrappedReq.getValue().getRequestURI()); - Assert.assertEquals("/folder/", wrappedReq.getValue().getHeader("Destination")); - } - - @Test - public void testMoveFolderToFileRequest() throws IOException, ServletException { - Mockito.when(request.getPathInfo()).thenReturn("/folder"); - Mockito.when(request.getRequestURI()).thenReturn("/folder"); - Mockito.when(request.getMethod()).thenReturn("COPY"); - Mockito.when(request.getHeader("Destination")).thenReturn("/file/"); - filter.doFilter(request, response, chain); - - ArgumentCaptor wrappedReq = ArgumentCaptor.forClass(HttpServletRequest.class); - Mockito.verify(chain).doFilter(wrappedReq.capture(), Mockito.any(ServletResponse.class)); - Assert.assertEquals("/folder/", wrappedReq.getValue().getRequestURI()); - Assert.assertEquals("/file", wrappedReq.getValue().getHeader("Destination")); - } - - /* UNKNOWN */ - - @Test - public void testUnknownPutRequest() throws IOException, ServletException { - Mockito.when(request.getPathInfo()).thenReturn("/404/"); - Mockito.when(request.getRequestURI()).thenReturn("/404/"); - Mockito.when(request.getMethod()).thenReturn("PUT"); - filter.doFilter(request, response, chain); - Mockito.verify(resourceTypeChecker).typeOfResource("/404/"); - - ArgumentCaptor wrappedReq = ArgumentCaptor.forClass(HttpServletRequest.class); - Mockito.verify(chain).doFilter(wrappedReq.capture(), Mockito.any(ServletResponse.class)); - Assert.assertEquals("/404", wrappedReq.getValue().getRequestURI()); - } - - @Test - public void testUnknownMkcolRequest() throws IOException, ServletException { - Mockito.when(request.getPathInfo()).thenReturn("/404"); - Mockito.when(request.getRequestURI()).thenReturn("/404"); - Mockito.when(request.getMethod()).thenReturn("MKCOL"); - filter.doFilter(request, response, chain); - Mockito.verify(resourceTypeChecker).typeOfResource("/404"); - - ArgumentCaptor wrappedReq = ArgumentCaptor.forClass(HttpServletRequest.class); - Mockito.verify(chain).doFilter(wrappedReq.capture(), Mockito.any(ServletResponse.class)); - Assert.assertEquals("/404/", wrappedReq.getValue().getRequestURI()); - } - - @Test - public void testUnknownPropfindRequest() throws IOException, ServletException { - Mockito.when(request.getPathInfo()).thenReturn("/404"); - Mockito.when(request.getRequestURI()).thenReturn("/404"); - Mockito.when(request.getMethod()).thenReturn("PROPFIND"); - filter.doFilter(request, response, chain); - Mockito.verify(resourceTypeChecker).typeOfResource("/404"); - - ArgumentCaptor wrappedReq = ArgumentCaptor.forClass(HttpServletRequest.class); - Mockito.verify(chain).doFilter(wrappedReq.capture(), Mockito.any(ServletResponse.class)); - Assert.assertSame(request, wrappedReq.getValue()); - } - -} diff --git a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/jackrabbitservlet/ExclusiveSharedLockManagerTest.java b/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/jackrabbitservlet/ExclusiveSharedLockManagerTest.java deleted file mode 100644 index 09d302937..000000000 --- a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/jackrabbitservlet/ExclusiveSharedLockManagerTest.java +++ /dev/null @@ -1,171 +0,0 @@ -/******************************************************************************* - * 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.frontend.webdav.jackrabbitservlet; - -import java.util.Optional; - -import org.apache.jackrabbit.webdav.DavException; -import org.apache.jackrabbit.webdav.DavServletResponse; -import org.apache.jackrabbit.webdav.lock.ActiveLock; -import org.apache.jackrabbit.webdav.lock.LockInfo; -import org.apache.jackrabbit.webdav.lock.Scope; -import org.apache.jackrabbit.webdav.lock.Type; -import org.cryptomator.filesystem.jackrabbit.FileLocator; -import org.cryptomator.filesystem.jackrabbit.FolderLocator; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; - -public class ExclusiveSharedLockManagerTest { - - private FolderLocator parentLocator; - private DavFolder parentResource; - private FileLocator childLocator; - private DavFile childResource; - private LockInfo lockInfo; - private ExclusiveSharedLockManager lockManager; - - @Before - public void setup() { - parentLocator = Mockito.mock(FolderLocator.class); - Mockito.when(parentLocator.name()).thenReturn("parent"); - Mockito.when(parentLocator.parent()).thenReturn(Optional.empty()); - - parentResource = Mockito.mock(DavFolder.class); - Mockito.when(parentResource.getLocator()).thenReturn(parentLocator); - - childLocator = Mockito.mock(FileLocator.class); - Mockito.when(childLocator.name()).thenReturn("child"); - Mockito.when(childLocator.parent()).thenReturn(Optional.of(parentLocator)); - Mockito.when(parentLocator.isAncestorOf(childLocator)).thenReturn(true); - - childResource = Mockito.mock(DavFile.class); - Mockito.when(childResource.getLocator()).thenReturn(childLocator); - - lockInfo = Mockito.mock(LockInfo.class); - Mockito.when(lockInfo.getScope()).thenReturn(Scope.EXCLUSIVE); - Mockito.when(lockInfo.getType()).thenReturn(Type.WRITE); - Mockito.when(lockInfo.getOwner()).thenReturn("test"); - Mockito.when(lockInfo.getTimeout()).thenReturn(3600l); - Mockito.when(lockInfo.isDeep()).thenReturn(true); - - lockManager = new ExclusiveSharedLockManager(); - } - - @Test - public void testLockCreation() throws DavException { - ActiveLock lock = lockManager.createLock(lockInfo, parentResource); - Assert.assertEquals(Scope.EXCLUSIVE, lock.getScope()); - Assert.assertEquals(Type.WRITE, lock.getType()); - Assert.assertEquals("test", lock.getOwner()); - Assert.assertFalse(lock.isExpired()); - Assert.assertTrue(lock.isDeep()); - } - - @Test - public void testLockCreationInParent() throws DavException { - ActiveLock lock = lockManager.createLock(lockInfo, parentResource); - - Assert.assertTrue(lockManager.hasLock(lock.getToken(), parentResource)); - Assert.assertFalse(lockManager.hasLock(lock.getToken(), childResource)); - - ActiveLock parentLock = lockManager.getLock(Type.WRITE, Scope.EXCLUSIVE, parentResource); - ActiveLock childLock = lockManager.getLock(Type.WRITE, Scope.EXCLUSIVE, childResource); - Assert.assertEquals(lock, parentLock); - Assert.assertEquals(lock, childLock); - } - - @Test - public void testLockCreationInChild() throws DavException { - ActiveLock lock = lockManager.createLock(lockInfo, childResource); - - Assert.assertTrue(lockManager.hasLock(lock.getToken(), childResource)); - Assert.assertFalse(lockManager.hasLock(lock.getToken(), parentResource)); - - ActiveLock parentLock = lockManager.getLock(Type.WRITE, Scope.EXCLUSIVE, parentResource); - ActiveLock childLock = lockManager.getLock(Type.WRITE, Scope.EXCLUSIVE, childResource); - Assert.assertNull(parentLock); - Assert.assertEquals(lock, childLock); - } - - @Test - public void testMultipleSharedLocks() throws DavException { - Mockito.when(lockInfo.getScope()).thenReturn(Scope.SHARED); - - ActiveLock lock1 = lockManager.createLock(lockInfo, parentResource); - ActiveLock lock2 = lockManager.createLock(lockInfo, childResource); - - Assert.assertTrue(lockManager.hasLock(lock1.getToken(), parentResource)); - Assert.assertTrue(lockManager.hasLock(lock2.getToken(), childResource)); - - Assert.assertNotEquals(lock1, lock2); - } - - @Test - public void testReleaseLock() throws DavException { - ActiveLock lock = lockManager.createLock(lockInfo, parentResource); - Assert.assertNotNull(lockManager.getLock(Type.WRITE, Scope.EXCLUSIVE, parentResource)); - - lockManager.releaseLock(lock.getToken(), parentResource); - Assert.assertNull(lockManager.getLock(Type.WRITE, Scope.EXCLUSIVE, parentResource)); - } - - @Test - public void testReleaseNonExistingLock() throws DavException { - lockManager.releaseLock("doesn't exist", parentResource); - Assert.assertNull(lockManager.getLock(Type.WRITE, Scope.EXCLUSIVE, parentResource)); - } - - @Test - public void testRefreshLock() throws DavException { - ActiveLock originalLock = lockManager.createLock(lockInfo, parentResource); - long originalTimeout = originalLock.getTimeout(); - - Mockito.when(lockInfo.getTimeout()).thenReturn(7200l); - ActiveLock updatedLock = lockManager.refreshLock(lockInfo, originalLock.getToken(), parentResource); - long updatedTimeout = updatedLock.getTimeout(); - - Assert.assertTrue(updatedTimeout > originalTimeout); - } - - @Test(expected = DavException.class) - public void testRefreshNonExistingLock() throws DavException { - lockManager.refreshLock(lockInfo, "doesn't exist", parentResource); - } - - @Test(expected = DavException.class) - public void testRefreshLockWithInvalidToken() throws DavException { - lockManager.createLock(lockInfo, parentResource); - lockManager.refreshLock(lockInfo, "doesn't exist", parentResource); - } - - @Test - public void testLockCreationWhenParentAlreadyLocked() throws DavException { - lockManager.createLock(lockInfo, parentResource); - try { - lockManager.createLock(lockInfo, childResource); - Assert.fail("Should have thrown excpetion"); - } catch (DavException e) { - Assert.assertEquals(DavServletResponse.SC_LOCKED, e.getErrorCode()); - } - } - - @Test - public void testLockCreationWhenChildAlreadyLocked() throws DavException { - lockManager.createLock(lockInfo, childResource); - try { - lockManager.createLock(lockInfo, parentResource); - Assert.fail("Should have thrown excpetion"); - } catch (DavException e) { - Assert.assertEquals(DavServletResponse.SC_CONFLICT, e.getErrorCode()); - } - } - -} diff --git a/main/frontend-webdav/src/test/resources/log4j2.xml b/main/frontend-webdav/src/test/resources/log4j2.xml deleted file mode 100644 index 39c2f8545..000000000 --- a/main/frontend-webdav/src/test/resources/log4j2.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/main/jacoco-report/pom.xml b/main/jacoco-report/pom.xml index 2f43205e7..17261c267 100644 --- a/main/jacoco-report/pom.xml +++ b/main/jacoco-report/pom.xml @@ -20,50 +20,6 @@ org.cryptomator commons-test - - - - org.cryptomator - filesystem-api - - - org.cryptomator - filesystem-charsets - - - org.cryptomator - filesystem-crypto - - - org.cryptomator - filesystem-crypto-integration-tests - - - org.cryptomator - filesystem-inmemory - - - org.cryptomator - filesystem-nameshortening - - - org.cryptomator - filesystem-nio - - - org.cryptomator - filesystem-stats - - - - - org.cryptomator - frontend-api - - - org.cryptomator - frontend-webdav - diff --git a/main/keychain/pom.xml b/main/keychain/pom.xml index 54160080d..69a209683 100644 --- a/main/keychain/pom.xml +++ b/main/keychain/pom.xml @@ -16,7 +16,6 @@ com.google.code.gson gson - 2.7 commons-codec diff --git a/main/pom.xml b/main/pom.xml index 000fc4a0c..f2ef3a13a 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -27,7 +27,9 @@ UTF-8 - 1.0.7 + 1.0.9 + 1.0.1 + 0.2.3 1.0.0 2.1 1.7.7 @@ -39,7 +41,6 @@ 3.4 1.10 3.1 - 2.4.4 1.10.19 2.6.1 @@ -71,64 +72,6 @@ ${project.version} test - - org.cryptomator - filesystem-api - ${project.version} - - - org.cryptomator - filesystem-charsets - ${project.version} - - - org.cryptomator - filesystem-nio - ${project.version} - - - org.cryptomator - filesystem-inmemory - ${project.version} - test - - - org.cryptomator - filesystem-invariants-tests - ${project.version} - test - - - org.cryptomator - filesystem-nameshortening - ${project.version} - - - org.cryptomator - filesystem-crypto - ${project.version} - - - org.cryptomator - filesystem-crypto-integration-tests - ${project.version} - test - - - org.cryptomator - filesystem-stats - ${project.version} - - - org.cryptomator - frontend-api - ${project.version} - - - org.cryptomator - frontend-webdav - ${project.version} - org.cryptomator keychain @@ -146,6 +89,16 @@ cryptolib ${cryptomator.cryptolib.version} + + org.cryptomator + cryptofs + ${cryptomator.cryptofs.version} + + + org.cryptomator + webdav-nio-adapter + ${cryptomator.webdav.version} + org.cryptomator jni @@ -231,9 +184,9 @@ - com.fasterxml.jackson.core - jackson-databind - ${jackson-databind.version} + com.google.code.gson + gson + 2.8.0 @@ -290,17 +243,6 @@ commons commons-test - filesystem-api - filesystem-inmemory - filesystem-nio - filesystem-nameshortening - filesystem-charsets - filesystem-crypto - filesystem-crypto-integration-tests - filesystem-stats - filesystem-invariants-tests - frontend-api - frontend-webdav keychain ui diff --git a/main/ui/pom.xml b/main/ui/pom.xml index 09efe9e1c..ef8134c78 100644 --- a/main/ui/pom.xml +++ b/main/ui/pom.xml @@ -24,35 +24,11 @@ org.cryptomator - filesystem-api + cryptofs org.cryptomator - filesystem-nio - - - org.cryptomator - filesystem-nameshortening - - - org.cryptomator - filesystem-crypto - - - org.cryptomator - filesystem-charsets - - - org.cryptomator - filesystem-stats - - - org.cryptomator - frontend-api - - - org.cryptomator - frontend-webdav + webdav-nio-adapter org.cryptomator @@ -77,8 +53,8 @@ - com.fasterxml.jackson.core - jackson-databind + com.google.code.gson + gson @@ -96,6 +72,10 @@ org.apache.commons commons-lang3 + + org.apache.commons + commons-collections4 + commons-httpclient commons-httpclient diff --git a/main/ui/src/main/java/org/cryptomator/ui/Cryptomator.java b/main/ui/src/main/java/org/cryptomator/ui/Cryptomator.java index 1f7e07531..90786f1f4 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/Cryptomator.java +++ b/main/ui/src/main/java/org/cryptomator/ui/Cryptomator.java @@ -15,15 +15,15 @@ import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.List; import java.util.Optional; -import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.function.Consumer; import org.apache.commons.lang3.SystemUtils; import org.cryptomator.ui.util.ApplicationVersion; import org.cryptomator.ui.util.SingleInstanceManager; import org.cryptomator.ui.util.SingleInstanceManager.RemoteInstance; -import org.eclipse.jetty.util.ConcurrentHashSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,7 +33,7 @@ public class Cryptomator { public static final CompletableFuture> OPEN_FILE_HANDLER = new CompletableFuture<>(); private static final Logger LOG = LoggerFactory.getLogger(Cryptomator.class); - private static final Set SHUTDOWN_TASKS = new ConcurrentHashSet<>(); + private static final ConcurrentMap SHUTDOWN_TASKS = new ConcurrentHashMap<>(); public static void main(String[] args) { LOG.info("Starting Cryptomator {} on {} {} ({})", ApplicationVersion.orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH); @@ -91,7 +91,7 @@ public class Cryptomator { } public static void addShutdownTask(Runnable r) { - SHUTDOWN_TASKS.add(r); + SHUTDOWN_TASKS.put(r, Boolean.TRUE); } public static void removeShutdownTask(Runnable r) { @@ -102,7 +102,7 @@ public class Cryptomator { @Override public void run() { LOG.debug("Shutting down"); - SHUTDOWN_TASKS.forEach(r -> { + SHUTDOWN_TASKS.keySet().forEach(r -> { try { r.run(); } catch (RuntimeException e) { diff --git a/main/ui/src/main/java/org/cryptomator/ui/CryptomatorComponent.java b/main/ui/src/main/java/org/cryptomator/ui/CryptomatorComponent.java index c4360fe56..830945bd3 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/CryptomatorComponent.java +++ b/main/ui/src/main/java/org/cryptomator/ui/CryptomatorComponent.java @@ -8,24 +8,20 @@ *******************************************************************************/ package org.cryptomator.ui; -import java.util.Optional; import java.util.concurrent.ExecutorService; import javax.inject.Singleton; -import org.cryptomator.jni.MacFunctions; import org.cryptomator.ui.controllers.MainController; -import org.cryptomator.ui.settings.Localization; -import org.cryptomator.ui.util.AsyncTaskService; +import org.cryptomator.ui.model.VaultComponent; +import org.cryptomator.ui.model.VaultModule; import org.cryptomator.ui.util.DeferredCloser; import dagger.Component; @Singleton @Component(modules = CryptomatorModule.class) -interface CryptomatorComponent { - - AsyncTaskService asyncTaskService(); +public interface CryptomatorComponent { ExecutorService executorService(); @@ -33,12 +29,10 @@ interface CryptomatorComponent { MainController mainController(); - Localization localization(); - ExitUtil exitUtil(); DebugMode debugMode(); - Optional nativeMacFunctions(); + VaultComponent newVaultComponent(VaultModule vaultModule); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/CryptomatorModule.java b/main/ui/src/main/java/org/cryptomator/ui/CryptomatorModule.java index 17fcb7ef6..f0681c4a8 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/CryptomatorModule.java +++ b/main/ui/src/main/java/org/cryptomator/ui/CryptomatorModule.java @@ -8,8 +8,6 @@ *******************************************************************************/ package org.cryptomator.ui; -import static java.util.stream.Collectors.toList; - import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -17,32 +15,22 @@ import javax.inject.Named; import javax.inject.Singleton; import org.cryptomator.common.CommonsModule; -import org.cryptomator.crypto.engine.impl.CryptoEngineModule; import org.cryptomator.cryptolib.CryptoLibModule; -import org.cryptomator.frontend.FrontendFactory; -import org.cryptomator.frontend.FrontendId; -import org.cryptomator.frontend.webdav.WebDavModule; import org.cryptomator.frontend.webdav.WebDavServer; import org.cryptomator.jni.JniModule; import org.cryptomator.keychain.KeychainModule; -import org.cryptomator.ui.model.Vault; -import org.cryptomator.ui.model.VaultObjectMapperProvider; -import org.cryptomator.ui.model.Vaults; import org.cryptomator.ui.settings.Settings; import org.cryptomator.ui.settings.SettingsProvider; import org.cryptomator.ui.util.DeferredCloser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.databind.ObjectMapper; - import dagger.Module; import dagger.Provides; import javafx.application.Application; -import javafx.beans.Observable; import javafx.stage.Stage; -@Module(includes = {CryptoEngineModule.class, CommonsModule.class, WebDavModule.class, KeychainModule.class, JniModule.class, CryptoLibModule.class}) +@Module(includes = {CommonsModule.class, KeychainModule.class, JniModule.class, CryptoLibModule.class}) class CryptomatorModule { private static final Logger LOG = LoggerFactory.getLogger(CryptomatorModule.class); @@ -81,13 +69,6 @@ class CryptomatorModule { return closer; } - @Provides - @Singleton - @Named("VaultJsonMapper") - ObjectMapper provideVaultObjectMapper(VaultObjectMapperProvider vaultObjectMapperProvider) { - return vaultObjectMapperProvider.get(); - } - @Provides @Singleton Settings provideSettings(SettingsProvider settingsProvider) { @@ -102,17 +83,8 @@ class CryptomatorModule { @Provides @Singleton - FrontendFactory provideFrontendFactory(DeferredCloser closer, WebDavServer webDavServer, Vaults vaults, Settings settings) { - vaults.addListener((Observable o) -> setValidFrontendIds(webDavServer, vaults)); - setValidFrontendIds(webDavServer, vaults); - webDavServer.setPort(settings.getPort()); - webDavServer.start(); - return closer.closeLater(webDavServer, WebDavServer::stop).get().orElseThrow(IllegalStateException::new); - } - - private void setValidFrontendIds(WebDavServer webDavServer, Vaults vaults) { - webDavServer.setValidFrontendIds(vaults.stream() // - .map(Vault::getId).map(FrontendId::from).collect(toList())); + WebDavServer provideWebDavServer(Settings settings) { + return WebDavServer.create("localhost", settings.getPort()); } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java index b254931fb..ba23bfdd1 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java @@ -18,8 +18,8 @@ import java.util.Optional; import javax.inject.Inject; import javax.inject.Singleton; -import org.cryptomator.crypto.engine.InvalidPassphraseException; -import org.cryptomator.crypto.engine.UnsupportedVaultFormatException; +import org.cryptomator.cryptolib.api.InvalidPassphraseException; +import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException; import org.cryptomator.ui.controls.SecPasswordField; import org.cryptomator.ui.model.Vault; import org.cryptomator.ui.settings.Localization; diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java index 9c9c194ba..9821cc005 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java @@ -29,9 +29,10 @@ import org.cryptomator.ui.controls.DirectoryListCell; import org.cryptomator.ui.model.UpgradeStrategies; import org.cryptomator.ui.model.Vault; import org.cryptomator.ui.model.VaultFactory; -import org.cryptomator.ui.model.Vaults; +import org.cryptomator.ui.model.VaultList; import org.cryptomator.ui.settings.Localization; import org.cryptomator.ui.settings.Settings; +import org.cryptomator.ui.settings.VaultSettings; import org.cryptomator.ui.util.DialogBuilderUtil; import org.fxmisc.easybind.EasyBind; import org.slf4j.Logger; @@ -79,7 +80,7 @@ public class MainController extends LocalizedFXMLViewController { private final Lazy settingsController; private final Lazy upgradeStrategies; private final ObjectProperty activeController = new SimpleObjectProperty<>(); - private final Vaults vaults; + private final VaultList vaults; private final ObjectProperty selectedVault = new SimpleObjectProperty<>(); private final BooleanExpression isSelectedVaultUnlocked = BooleanBinding.booleanExpression(EasyBind.select(selectedVault).selectObject(Vault::unlockedProperty).orElse(false)); private final BooleanExpression isSelectedVaultValid = BooleanBinding.booleanExpression(EasyBind.monadic(selectedVault).map(Vault::isValidVaultDirectory).orElse(false)); @@ -91,7 +92,7 @@ public class MainController extends LocalizedFXMLViewController { public MainController(@Named("mainWindow") Stage mainWindow, Localization localization, Settings settings, VaultFactory vaultFactoy, Lazy welcomeController, Lazy initializeController, Lazy notFoundController, Lazy upgradeController, Lazy unlockController, Provider unlockedControllerProvider, Lazy changePasswordController, Lazy settingsController, Lazy upgradeStrategies, - Vaults vaults) { + VaultList vaults) { super(localization); this.mainWindow = mainWindow; this.vaultFactoy = vaultFactoy; @@ -230,7 +231,9 @@ public class MainController extends LocalizedFXMLViewController { return; } - final Vault vault = vaultFactoy.createVault(vaultPath); + final VaultSettings vaultSettings = VaultSettings.withRandomId(); + vaultSettings.setPath(vaultPath); + final Vault vault = vaultFactoy.get(vaultSettings); if (!vaults.contains(vault)) { vaults.add(vault); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java index 72341c74f..0628b7187 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java @@ -18,15 +18,13 @@ import javax.inject.Inject; import org.apache.commons.lang3.CharUtils; import org.apache.commons.lang3.SystemUtils; -import org.cryptomator.crypto.engine.InvalidPassphraseException; -import org.cryptomator.crypto.engine.UnsupportedVaultFormatException; -import org.cryptomator.frontend.CommandFailedException; -import org.cryptomator.frontend.FrontendCreationFailedException; -import org.cryptomator.frontend.FrontendFactory; -import org.cryptomator.frontend.webdav.mount.WindowsDriveLetters; +import org.cryptomator.cryptolib.api.InvalidPassphraseException; +import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException; +import org.cryptomator.frontend.webdav.ServerLifecycleException; import org.cryptomator.keychain.KeychainAccess; import org.cryptomator.ui.controls.SecPasswordField; import org.cryptomator.ui.model.Vault; +import org.cryptomator.ui.model.WindowsDriveLetters; import org.cryptomator.ui.settings.Localization; import org.cryptomator.ui.settings.Settings; import org.cryptomator.ui.util.AsyncTaskService; @@ -34,7 +32,6 @@ import org.cryptomator.ui.util.DialogBuilderUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import dagger.Lazy; import javafx.application.Application; import javafx.application.Platform; import javafx.beans.value.ChangeListener; @@ -61,7 +58,6 @@ public class UnlockController extends LocalizedFXMLViewController { private final Application app; private final AsyncTaskService asyncTaskService; - private final Lazy frontendFactory; private final Settings settings; private final WindowsDriveLetters driveLetters; private final ChangeListener driveLetterChangeListener = this::winDriveLetterDidChange; @@ -70,12 +66,10 @@ public class UnlockController extends LocalizedFXMLViewController { private Optional listener = Optional.empty(); @Inject - public UnlockController(Application app, Localization localization, AsyncTaskService asyncTaskService, Lazy frontendFactory, Settings settings, WindowsDriveLetters driveLetters, - Optional keychainAccess) { + public UnlockController(Application app, Localization localization, AsyncTaskService asyncTaskService, Settings settings, WindowsDriveLetters driveLetters, Optional keychainAccess) { super(localization); this.app = app; this.asyncTaskService = asyncTaskService; - this.frontendFactory = frontendFactory; this.settings = settings; this.driveLetters = driveLetters; this.keychainAccess = keychainAccess; @@ -326,7 +320,7 @@ public class UnlockController extends LocalizedFXMLViewController { private void unlock(CharSequence password) { try { - vault.activateFrontend(frontendFactory.get(), settings, password); + vault.activateFrontend(password); vault.reveal(); Platform.runLater(() -> { messageText.setText(null); @@ -356,8 +350,8 @@ public class UnlockController extends LocalizedFXMLViewController { messageText.setText(localization.getString("unlock.errorMessage.unauthenticVersionMac")); } }); - } catch (FrontendCreationFailedException | CommandFailedException e) { - LOG.error("Decryption failed for technical reasons.", e); + } catch (ServerLifecycleException e) { + LOG.error("Unlock failed for technical reasons.", e); Platform.runLater(() -> { messageText.setText(localization.getString("unlock.errorMessage.mountingFailed")); }); diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java index 610260092..b160804bd 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java @@ -14,7 +14,6 @@ import java.util.Optional; import javax.inject.Inject; import javax.inject.Provider; -import org.cryptomator.frontend.CommandFailedException; import org.cryptomator.ui.model.Vault; import org.cryptomator.ui.settings.Localization; import org.cryptomator.ui.util.ActiveWindowStyleSupport; @@ -107,13 +106,16 @@ public class UnlockedController extends LocalizedFXMLViewController { } // listen to MAC warnings as long as this vault is unlocked: - final ListChangeListener macWarningsListener = this::macWarningsDidChange; - newVault.getNamesOfResourcesWithInvalidMac().addListener(macWarningsListener); - newVault.unlockedProperty().addListener((observable, oldValue, newValue) -> { - if (Boolean.FALSE.equals(newValue)) { - newVault.getNamesOfResourcesWithInvalidMac().removeListener(macWarningsListener); - } - }); + // TODO overheadhunter: reimplement eventually + /* + * final ListChangeListener macWarningsListener = this::macWarningsDidChange; + * newVault.getNamesOfResourcesWithInvalidMac().addListener(macWarningsListener); + * newVault.unlockedProperty().addListener((observable, oldValue, newValue) -> { + * if (Boolean.FALSE.equals(newValue)) { + * newVault.getNamesOfResourcesWithInvalidMac().removeListener(macWarningsListener); + * } + * }); + */ if (!vault.get().isMounted()) { // TODO Markus Kreusch #393: hyperlink auf FAQ oder sowas? @@ -150,7 +152,8 @@ public class UnlockedController extends LocalizedFXMLViewController { private void didClickRevealVault(ActionEvent event) { asyncTaskService.asyncTaskOf(() -> { vault.get().reveal(); - }).onError(CommandFailedException.class, () -> { + }).onError(RuntimeException.class, () -> { + // TODO overheadhunter catch more specific exception type thrown by reveal() messageLabel.setText(localization.getString("unlocked.label.revealFailed")); }).run(); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/UpgradeController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/UpgradeController.java index 48dc21fa5..1890be6e2 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/UpgradeController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/UpgradeController.java @@ -116,7 +116,7 @@ public class UpgradeController extends LocalizedFXMLViewController { asyncTaskService // .asyncTaskOf(() -> { if (!instruction.isApplicable(vault)) { - throw new IllegalStateException("No ugprade needed for " + vault.path().getValue()); + throw new IllegalStateException("No ugprade needed for " + vault.getPath()); } instruction.upgrade(vault, passwordField.getCharacters()); }) // diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java index e1a59956f..a163f02e0 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java @@ -9,9 +9,11 @@ package org.cryptomator.ui.controllers; import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.Comparator; -import java.util.HashMap; import java.util.Map; import javax.inject.Inject; @@ -24,7 +26,6 @@ import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.cookie.CookiePolicy; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.params.HttpClientParams; -import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.SystemUtils; import org.cryptomator.ui.settings.Localization; import org.cryptomator.ui.settings.Settings; @@ -33,8 +34,9 @@ import org.cryptomator.ui.util.AsyncTaskService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; import javafx.application.Application; import javafx.application.Platform; @@ -110,10 +112,10 @@ public class WelcomeController extends LocalizedFXMLViewController { client.executeMethod(method); final InputStream responseBodyStream = method.getResponseBodyAsStream(); if (method.getStatusCode() == HttpStatus.SC_OK && responseBodyStream != null) { - final byte[] responseData = IOUtils.toByteArray(responseBodyStream); - final ObjectMapper mapper = new ObjectMapper(); - final Map map = mapper.readValue(responseData, new TypeReference>() { - }); + Gson gson = new GsonBuilder().setLenient().create(); + Reader utf8Reader = new InputStreamReader(responseBodyStream, StandardCharsets.UTF_8); + Map map = gson.fromJson(utf8Reader, new TypeToken>() { + }.getType()); if (map != null) { this.compareVersions(map); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/controls/DirectoryListCell.java b/main/ui/src/main/java/org/cryptomator/ui/controls/DirectoryListCell.java index 1df6e7dd4..81a8ac281 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controls/DirectoryListCell.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controls/DirectoryListCell.java @@ -59,7 +59,7 @@ public class DirectoryListCell extends DraggableListCell { statusIndicator.fillProperty().bind(EasyBind.monadic(itemProperty()).flatMap(Vault::unlockedProperty).filter(Boolean.TRUE::equals).map(unlocked -> GREEN_FILL).orElse(RED_FILL)); statusIndicator.strokeProperty().bind(EasyBind.monadic(itemProperty()).flatMap(Vault::unlockedProperty).filter(Boolean.TRUE::equals).map(unlocked -> GREEN_STROKE).orElse(RED_STROKE)); - tooltipProperty().bind(EasyBind.monadic(itemProperty()).flatMap(Vault::path).map(p -> new Tooltip(p.toString()))); + tooltipProperty().bind(EasyBind.monadic(itemProperty()).flatMap(Vault::displayablePath).map(p -> new Tooltip(p.toString()))); contextMenuProperty().bind(EasyBind.monadic(itemProperty()).flatMap(Vault::unlockedProperty).map(unlocked -> { return unlocked ? null : vaultContextMenu; })); diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeStrategy.java b/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeStrategy.java index 2ef55dd77..eea0a8866 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeStrategy.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeStrategy.java @@ -11,7 +11,6 @@ import org.cryptomator.cryptolib.api.CryptorProvider; import org.cryptomator.cryptolib.api.InvalidPassphraseException; import org.cryptomator.cryptolib.api.KeyFile; import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException; -import org.cryptomator.filesystem.crypto.Constants; import org.cryptomator.ui.settings.Localization; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -19,6 +18,8 @@ import org.slf4j.LoggerFactory; public abstract class UpgradeStrategy { private static final Logger LOG = LoggerFactory.getLogger(UpgradeStrategy.class); + private static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; + private static final String MASTERKEY_BACKUP_FILENAME = "masterkey.cryptomator.bkup"; protected final CryptorProvider cryptorProvider; protected final Localization localization; @@ -46,28 +47,28 @@ public abstract class UpgradeStrategy { * Upgrades a vault. Might take a moment, should be run in a background thread. */ public void upgrade(Vault vault, CharSequence passphrase) throws UpgradeFailedException { - LOG.info("Upgrading {} from {} to {}.", vault.path().getValue(), vaultVersionBeforeUpgrade, vaultVersionAfterUpgrade); + LOG.info("Upgrading {} from {} to {}.", vault.getPath(), vaultVersionBeforeUpgrade, vaultVersionAfterUpgrade); Cryptor cryptor = null; try { - final Path masterkeyFile = vault.path().getValue().resolve(Constants.MASTERKEY_FILENAME); + final Path masterkeyFile = vault.getPath().resolve(MASTERKEY_FILENAME); final byte[] masterkeyFileContents = Files.readAllBytes(masterkeyFile); cryptor = cryptorProvider.createFromKeyFile(KeyFile.parse(masterkeyFileContents), passphrase, vaultVersionBeforeUpgrade); // create backup, as soon as we know the password was correct: - final Path masterkeyBackupFile = vault.path().getValue().resolve(Constants.MASTERKEY_BACKUP_FILENAME); + final Path masterkeyBackupFile = vault.getPath().resolve(MASTERKEY_BACKUP_FILENAME); Files.copy(masterkeyFile, masterkeyBackupFile, StandardCopyOption.REPLACE_EXISTING); LOG.info("Backuped masterkey."); // do stuff: upgrade(vault, cryptor); // write updated masterkey file: final byte[] upgradedMasterkeyFileContents = cryptor.writeKeysToMasterkeyFile(passphrase, vaultVersionAfterUpgrade).serialize(); - final Path masterkeyFileAfterUpgrade = vault.path().getValue().resolve(Constants.MASTERKEY_FILENAME); // path may have changed + final Path masterkeyFileAfterUpgrade = vault.getPath().resolve(MASTERKEY_FILENAME); // path may have changed Files.write(masterkeyFileAfterUpgrade, upgradedMasterkeyFileContents, StandardOpenOption.TRUNCATE_EXISTING); LOG.info("Updated masterkey."); } catch (InvalidPassphraseException e) { throw new UpgradeFailedException(localization.getString("unlock.errorMessage.wrongPassword")); } catch (UnsupportedVaultFormatException e) { if (e.getDetectedVersion() == Integer.MAX_VALUE) { - LOG.warn("Version MAC authentication error in vault {}", vault.path().get()); + LOG.warn("Version MAC authentication error in vault {}", vault.getPath()); throw new UpgradeFailedException(localization.getString("unlock.errorMessage.unauthenticVersionMac")); } else { LOG.warn("Upgrade failed.", e); @@ -91,7 +92,7 @@ public abstract class UpgradeStrategy { * @return true if and only if the vault can be migrated to a newer version without the risk of data losses. */ public boolean isApplicable(Vault vault) { - final Path masterkeyFile = vault.path().getValue().resolve(Constants.MASTERKEY_FILENAME); + final Path masterkeyFile = vault.getPath().resolve(MASTERKEY_FILENAME); try { if (Files.isRegularFile(masterkeyFile)) { byte[] masterkeyFileContents = Files.readAllBytes(masterkeyFile); diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion3DropBundleExtension.java b/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion3DropBundleExtension.java index 60087419f..5f22c917e 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion3DropBundleExtension.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion3DropBundleExtension.java @@ -39,7 +39,7 @@ class UpgradeVersion3DropBundleExtension extends UpgradeStrategy { @Override public String getMessage(Vault vault) { String fmt = localization.getString("upgrade.version3dropBundleExtension.msg"); - Path path = vault.path().getValue(); + Path path = vault.getPath(); String oldVaultName = path.getFileName().toString(); String newVaultName = StringUtils.removeEnd(oldVaultName, Vault.VAULT_FILE_EXTENSION); return String.format(fmt, oldVaultName, newVaultName); @@ -47,7 +47,7 @@ class UpgradeVersion3DropBundleExtension extends UpgradeStrategy { @Override protected void upgrade(Vault vault, Cryptor cryptor) throws UpgradeFailedException { - Path path = vault.path().getValue(); + Path path = vault.getPath(); String oldVaultName = path.getFileName().toString(); String newVaultName = StringUtils.removeEnd(oldVaultName, Vault.VAULT_FILE_EXTENSION); Path newPath = path.resolveSibling(newVaultName); @@ -60,7 +60,7 @@ class UpgradeVersion3DropBundleExtension extends UpgradeStrategy { LOG.info("Renaming {} to {}", path, newPath.getFileName()); Files.move(path, path.resolveSibling(newVaultName)); Platform.runLater(() -> { - vault.setPath(newPath); + vault.getVaultSettings().setPath(newPath); settings.save(); }); } catch (IOException e) { @@ -72,7 +72,7 @@ class UpgradeVersion3DropBundleExtension extends UpgradeStrategy { @Override public boolean isApplicable(Vault vault) { - Path vaultPath = vault.path().getValue(); + Path vaultPath = vault.getPath(); if (vaultPath.toString().endsWith(Vault.VAULT_FILE_EXTENSION)) { return super.isApplicable(vault); } else { diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion3to4.java b/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion3to4.java index 0ba05a58f..c7b8d2844 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion3to4.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion3to4.java @@ -61,8 +61,8 @@ class UpgradeVersion3to4 extends UpgradeStrategy { @Override protected void upgrade(Vault vault, Cryptor cryptor) throws UpgradeFailedException { - Path dataDir = vault.path().get().resolve("d"); - Path metadataDir = vault.path().get().resolve("m"); + Path dataDir = vault.getPath().resolve("d"); + Path metadataDir = vault.getPath().resolve("m"); if (!Files.isDirectory(dataDir)) { return; // empty vault. no migration needed. } diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion4to5.java b/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion4to5.java index 936a2c8ba..0fa7cd7b6 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion4to5.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion4to5.java @@ -51,7 +51,7 @@ class UpgradeVersion4to5 extends UpgradeStrategy { @Override protected void upgrade(Vault vault, Cryptor cryptor) throws UpgradeFailedException { - Path dataDir = vault.path().get().resolve("d"); + Path dataDir = vault.getPath().resolve("d"); if (!Files.isDirectory(dataDir)) { return; // empty vault. no migration needed. } @@ -76,6 +76,7 @@ class UpgradeVersion4to5 extends UpgradeStrategy { LOG.info("Migration finished."); } + @SuppressWarnings("deprecation") private void migrate(Path file, BasicFileAttributes attrs, Cryptor cryptor) throws IOException { LOG.info("Starting migration of {}...", file); try (FileChannel ch = FileChannel.open(file, StandardOpenOption.READ, StandardOpenOption.WRITE)) { 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 0d36158fc..4d739d44b 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 @@ -8,96 +8,61 @@ *******************************************************************************/ package org.cryptomator.ui.model; -import static org.apache.commons.lang3.StringUtils.stripStart; - import java.io.IOException; import java.io.UncheckedIOException; -import java.nio.file.DirectoryNotEmptyException; -import java.nio.file.FileAlreadyExistsException; +import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.text.Normalizer; import java.text.Normalizer.Form; -import java.util.HashSet; -import java.util.Map; -import java.util.Optional; +import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; -import org.apache.commons.lang3.CharUtils; +import javax.inject.Inject; + import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.LazyInitializer; -import org.cryptomator.common.Optionals; -import org.cryptomator.crypto.engine.InvalidPassphraseException; -import org.cryptomator.filesystem.File; -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; -import org.cryptomator.filesystem.shortening.ShorteningFileSystemFactory; -import org.cryptomator.filesystem.stats.StatsFileSystem; -import org.cryptomator.frontend.CommandFailedException; -import org.cryptomator.frontend.Frontend; -import org.cryptomator.frontend.Frontend.MountParam; -import org.cryptomator.frontend.FrontendCreationFailedException; -import org.cryptomator.frontend.FrontendFactory; -import org.cryptomator.frontend.FrontendId; -import org.cryptomator.ui.settings.Settings; -import org.cryptomator.ui.util.DeferredClosable; +import org.cryptomator.cryptofs.CryptoFileSystem; +import org.cryptomator.cryptofs.CryptoFileSystemProperties; +import org.cryptomator.cryptofs.CryptoFileSystemProvider; +import org.cryptomator.cryptolib.api.InvalidPassphraseException; +import org.cryptomator.frontend.webdav.WebDavServer; +import org.cryptomator.ui.model.VaultModule.PerVault; +import org.cryptomator.ui.settings.VaultSettings; import org.cryptomator.ui.util.DeferredCloser; -import org.cryptomator.ui.util.FXThreads; import org.fxmisc.easybind.EasyBind; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.collect.ImmutableMap; - import javafx.application.Platform; import javafx.beans.binding.Binding; import javafx.beans.property.BooleanProperty; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.collections.FXCollections; import javafx.collections.ObservableList; -public class Vault implements CryptoFileSystemDelegate { - - private static final Logger LOG = LoggerFactory.getLogger(CryptoFileSystemDelegate.class); +@PerVault +public class Vault { + private static final Logger LOG = LoggerFactory.getLogger(Vault.class); public static final String VAULT_FILE_EXTENSION = ".cryptomator"; - private final ObjectProperty path; - private final ShorteningFileSystemFactory shorteningFileSystemFactory; - private final CryptoFileSystemFactory cryptoFileSystemFactory; + private final VaultSettings vaultSettings; + private final WebDavServer server; private final DeferredCloser closer; + private final BooleanProperty unlocked = new SimpleBooleanProperty(); private final BooleanProperty mounted = new SimpleBooleanProperty(); - private final ObservableList namesOfResourcesWithInvalidMac = FXThreads.observableListOnMainThread(FXCollections.observableArrayList()); - private final Set whitelistedResourcesWithInvalidMac = new HashSet<>(); - private final AtomicReference nioFileSystem = new AtomicReference<>(); - private final String id; - private String mountName; - private Character winDriveLetter; - private Optional statsFileSystem = Optional.empty(); - private DeferredClosable filesystemFrontend = DeferredClosable.empty(); + private final AtomicReference cryptoFileSystem = new AtomicReference<>(); - /** - * Package private constructor, use {@link VaultFactory}. - * - * @param string - */ - Vault(String id, Path vaultDirectoryPath, ShorteningFileSystemFactory shorteningFileSystemFactory, CryptoFileSystemFactory cryptoFileSystemFactory, DeferredCloser closer) { - this.path = new SimpleObjectProperty(vaultDirectoryPath); - this.shorteningFileSystemFactory = shorteningFileSystemFactory; - this.cryptoFileSystemFactory = cryptoFileSystemFactory; + @Inject + Vault(VaultSettings vaultSettings, WebDavServer server, DeferredCloser closer) { + this.vaultSettings = vaultSettings; + this.server = server; this.closer = closer; - this.id = id; try { setMountName(name().getValue()); } catch (IllegalArgumentException e) { @@ -105,129 +70,92 @@ public class Vault implements CryptoFileSystemDelegate { } } - private FileSystem getNioFileSystem() { - return LazyInitializer.initializeLazily(nioFileSystem, () -> NioFileSystem.rootedAt(path.getValue())); - } - // ****************************************************************************** // Commands // ********************************************************************************/ + private CryptoFileSystem getCryptoFileSystem(CharSequence passphrase) throws IOException { + return LazyInitializer.initializeLazily(cryptoFileSystem, () -> createCryptoFileSystem(passphrase), IOException.class); + } + + private CryptoFileSystem createCryptoFileSystem(CharSequence passphrase) throws IOException { + CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties().withPassphrase(passphrase).build(); + CryptoFileSystem fs = CryptoFileSystemProvider.newFileSystem(getPath(), fsProps); + closer.closeLater(fs); + return fs; + } + public void create(CharSequence passphrase) throws IOException { - try { - FileSystem fs = getNioFileSystem(); - if (fs.files().map(File::name).filter(s -> s.endsWith(VAULT_FILE_EXTENSION)).count() > 0) { - throw new FileAlreadyExistsException("masterkey.cryptomator", null, "Vault location not empty."); - } else if (fs.folders().count() > 0) { - throw new DirectoryNotEmptyException(fs.toString()); - } - cryptoFileSystemFactory.initializeNew(fs, passphrase); - } catch (UncheckedIOException e) { - throw new IOException(e); - } + getCryptoFileSystem(passphrase); + // TODO and now? } public void changePassphrase(CharSequence oldPassphrase, CharSequence newPassphrase) throws IOException, InvalidPassphraseException { - try { - cryptoFileSystemFactory.changePassphrase(getNioFileSystem(), oldPassphrase, newPassphrase); - } catch (UncheckedIOException e) { - throw new IOException(e); - } + // TODO implement } - public synchronized void activateFrontend(FrontendFactory frontendFactory, Settings settings, CharSequence passphrase) throws FrontendCreationFailedException { + public synchronized void activateFrontend(CharSequence passphrase) { boolean launchSuccess = false; boolean mountSuccess = false; try { - FileSystem fs = getNioFileSystem(); - FileSystem shorteningFs = shorteningFileSystemFactory.get(fs); - FileSystem cryptoFs = cryptoFileSystemFactory.unlockExisting(shorteningFs, passphrase, this); - FileSystem normalizingFs = new NormalizedNameFileSystem(cryptoFs, SystemUtils.IS_OS_MAC_OSX ? Form.NFD : Form.NFC); - StatsFileSystem statsFs = new StatsFileSystem(normalizingFs); - statsFileSystem = Optional.of(statsFs); - Frontend frontend = frontendFactory.create(statsFs, FrontendId.from(id), stripStart(mountName, "/")); - launchSuccess = true; - filesystemFrontend = closer.closeLater(frontend); - frontend.mount(getMountParams(settings)); - mountSuccess = true; - } catch (UncheckedIOException e) { - throw new FrontendCreationFailedException(e); - } catch (CommandFailedException e) { - LOG.error("Failed to mount vault " + mountName, e); + FileSystem fs = getCryptoFileSystem(passphrase); + if (!server.isRunning()) { + server.start(); + } + server.startWebDavServlet(fs.getPath("/"), vaultSettings.getId()); + } catch (IOException e) { + LOG.error("Unable to provide frontend", e); } finally { // unlocked is a observable property and should only be changed by the FX application thread - boolean finalLaunchSuccess = launchSuccess; - boolean finalMountSuccess = mountSuccess; Platform.runLater(() -> { - unlocked.set(finalLaunchSuccess); - mounted.set(finalMountSuccess); + unlocked.set(launchSuccess); + mounted.set(mountSuccess); }); } } public synchronized void deactivateFrontend() throws Exception { - filesystemFrontend.close(); - statsFileSystem = Optional.empty(); + CryptoFileSystem fs = cryptoFileSystem.getAndSet(null); + if (fs != null) { + fs.close(); + } + // TODO overheadhunter remove servlet from server Platform.runLater(() -> { mounted.set(false); unlocked.set(false); }); } - private Map> getMountParams(Settings settings) { - String hostname = SystemUtils.IS_OS_WINDOWS && settings.shouldUseIpv6() ? "0--1.ipv6-literal.net" : "localhost"; - return ImmutableMap.of( // - MountParam.MOUNT_NAME, Optional.ofNullable(mountName), // - MountParam.WIN_DRIVE_LETTER, Optional.ofNullable(CharUtils.toString(winDriveLetter)), // - MountParam.HOSTNAME, Optional.of(hostname), // - MountParam.PREFERRED_GVFS_SCHEME, Optional.ofNullable(settings.getPreferredGvfsScheme()) // - ); - } - - public synchronized void reveal() throws CommandFailedException { - Optionals.ifPresent(filesystemFrontend.get(), Frontend::reveal); - } - - // ****************************************************************************** - // Delegate methods - // ********************************************************************************/ - - @Override - public void authenticationFailed(String cleartextPath) { - namesOfResourcesWithInvalidMac.add(cleartextPath); - } - - @Override - public boolean shouldSkipAuthentication(String cleartextPath) { - return whitelistedResourcesWithInvalidMac.contains(cleartextPath); + public synchronized void reveal() { + // TODO implement } // ****************************************************************************** // Getter/Setter // *******************************************************************************/ + public VaultSettings getVaultSettings() { + return vaultSettings; + } + public synchronized String getWebDavUrl() { - return filesystemFrontend.get().map(Frontend::getWebDavUrl).orElseThrow(IllegalStateException::new); + // TODO implement + return "http://localhost/not/implemented"; } - void setPath(Path path) { - this.path.set(path); - this.nioFileSystem.set(null); - } - - public ReadOnlyObjectProperty path() { - return path; + public Path getPath() { + return vaultSettings.pathProperty().getValue(); } public Binding displayablePath() { Path homeDir = Paths.get(SystemUtils.USER_HOME); - return EasyBind.map(path, p -> { + return EasyBind.map(vaultSettings.pathProperty(), p -> { if (p.startsWith(homeDir)) { Path relativePath = homeDir.relativize(p); String homePrefix = SystemUtils.IS_OS_WINDOWS ? "~\\" : "~/"; return homePrefix + relativePath.toString(); } else { - return path.getValue().toString(); + return p.toString(); } }); } @@ -236,16 +164,16 @@ public class Vault implements CryptoFileSystemDelegate { * @return Directory name without preceeding path components and file extension */ public Binding name() { - return EasyBind.map(path, p -> p.getFileName().toString()); + return EasyBind.map(vaultSettings.pathProperty(), p -> p.getFileName().toString()); } public boolean doesVaultDirectoryExist() { - return Files.isDirectory(path.getValue()); + return Files.isDirectory(getPath()); } public boolean isValidVaultDirectory() { try { - return doesVaultDirectoryExist() && cryptoFileSystemFactory.isValidVaultStructure(getNioFileSystem()); + return doesVaultDirectoryExist(); // TODO: && cryptoFileSystemFactory.isValidVaultStructure(getNioFileSystem()); } catch (UncheckedIOException e) { return false; } @@ -268,19 +196,23 @@ public class Vault implements CryptoFileSystemDelegate { } public ObservableList getNamesOfResourcesWithInvalidMac() { - return namesOfResourcesWithInvalidMac; + // TODO overheadhunter implement. + return null; } public Set getWhitelistedResourcesWithInvalidMac() { - return whitelistedResourcesWithInvalidMac; + // TODO overheadhunter implement. + return null; } public long pollBytesRead() { - return statsFileSystem.map(StatsFileSystem::getThenResetBytesRead).orElse(0l); + // TODO overheadhunter implement. + return 0l; } public long pollBytesWritten() { - return statsFileSystem.map(StatsFileSystem::getThenResetBytesWritten).orElse(0l); + // TODO overheadhunter implement. + return 0l; } /** @@ -310,7 +242,7 @@ public class Vault implements CryptoFileSystemDelegate { } public String getMountName() { - return mountName; + return vaultSettings.getMountName(); } /** @@ -320,23 +252,32 @@ public class Vault implements CryptoFileSystemDelegate { * @throws IllegalArgumentException if the name is empty after normalization */ public void setMountName(String mountName) throws IllegalArgumentException { - mountName = normalize(mountName); - if (StringUtils.isEmpty(mountName)) { + String normalized = normalize(mountName); + if (StringUtils.isEmpty(normalized)) { throw new IllegalArgumentException("mount name is empty"); + } else { + vaultSettings.setMountName(normalized); } - this.mountName = mountName; } public Character getWinDriveLetter() { - return winDriveLetter; + if (vaultSettings.getWinDriveLetter() == null) { + return null; + } else { + return vaultSettings.getWinDriveLetter().charAt(0); + } } public void setWinDriveLetter(Character winDriveLetter) { - this.winDriveLetter = winDriveLetter; + if (winDriveLetter == null) { + vaultSettings.setWinDriveLetter(null); + } else { + vaultSettings.setWinDriveLetter(String.valueOf(winDriveLetter)); + } } public String getId() { - return id; + return vaultSettings.getId(); } // ****************************************************************************** @@ -345,14 +286,14 @@ public class Vault implements CryptoFileSystemDelegate { @Override public int hashCode() { - return path.getValue().hashCode(); + return Objects.hash(vaultSettings); } @Override public boolean equals(Object obj) { - if (obj instanceof Vault) { + if (obj instanceof Vault && obj.getClass().equals(this.getClass())) { final Vault other = (Vault) obj; - return this.path.getValue().equals(other.path.getValue()); + return Objects.equals(this.vaultSettings, other.vaultSettings); } else { return false; } diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/VaultComponent.java b/main/ui/src/main/java/org/cryptomator/ui/model/VaultComponent.java new file mode 100644 index 000000000..9ecf3dcad --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/model/VaultComponent.java @@ -0,0 +1,13 @@ +package org.cryptomator.ui.model; + +import org.cryptomator.ui.model.VaultModule.PerVault; + +import dagger.Subcomponent; + +@PerVault +@Subcomponent(modules = {VaultModule.class}) +public interface VaultComponent { + + Vault vault(); + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/VaultFactory.java b/main/ui/src/main/java/org/cryptomator/ui/model/VaultFactory.java index cf1dcc756..51d3f8df6 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/VaultFactory.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/VaultFactory.java @@ -8,36 +8,34 @@ *******************************************************************************/ package org.cryptomator.ui.model; -import java.nio.file.Path; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import javax.inject.Inject; import javax.inject.Singleton; -import org.cryptomator.filesystem.crypto.CryptoFileSystemFactory; -import org.cryptomator.filesystem.shortening.ShorteningFileSystemFactory; -import org.cryptomator.frontend.FrontendId; -import org.cryptomator.ui.util.DeferredCloser; +import org.cryptomator.ui.CryptomatorComponent; +import org.cryptomator.ui.settings.VaultSettings; @Singleton public class VaultFactory { - private final ShorteningFileSystemFactory shorteningFileSystemFactory; - private final CryptoFileSystemFactory cryptoFileSystemFactory; - private final DeferredCloser closer; + private final CryptomatorComponent cryptomatorComponent; + private final ConcurrentMap vaults = new ConcurrentHashMap<>(); @Inject - public VaultFactory(ShorteningFileSystemFactory shorteningFileSystemFactory, CryptoFileSystemFactory cryptoFileSystemFactory, DeferredCloser closer) { - this.shorteningFileSystemFactory = shorteningFileSystemFactory; - this.cryptoFileSystemFactory = cryptoFileSystemFactory; - this.closer = closer; + public VaultFactory(CryptomatorComponent cryptomatorComponent) { + this.cryptomatorComponent = cryptomatorComponent; } - public Vault createVault(String id, Path path) { - return new Vault(id, path, shorteningFileSystemFactory, cryptoFileSystemFactory, closer); + public Vault get(VaultSettings vaultSettings) { + return vaults.computeIfAbsent(vaultSettings, this::create); } - public Vault createVault(Path path) { - return createVault(FrontendId.generate().toString(), path); + private Vault create(VaultSettings vaultSettings) { + VaultModule module = new VaultModule(vaultSettings); + VaultComponent comp = cryptomatorComponent.newVaultComponent(module); + return comp.vault(); } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/VaultList.java b/main/ui/src/main/java/org/cryptomator/ui/model/VaultList.java new file mode 100644 index 000000000..5d1f7d904 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/model/VaultList.java @@ -0,0 +1,121 @@ +package org.cryptomator.ui.model; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.cryptomator.ui.settings.Settings; +import org.cryptomator.ui.settings.VaultSettings; + +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener.Change; +import javafx.collections.ObservableList; +import javafx.collections.transformation.TransformationList; + +@Singleton +public class VaultList extends TransformationList { + + private final VaultFactory vaultFactory; + private final ObservableList source; + + @Inject + public VaultList(Settings settings, VaultFactory vaultFactory) { + this(FXCollections.observableList(settings.getDirectories()), settings, vaultFactory); + } + + private VaultList(ObservableList source, Settings settings, VaultFactory vaultFactory) { + super(source); + this.source = source; + this.vaultFactory = vaultFactory; + addListener((Change change) -> settings.save()); + } + + @Override + public int getSourceIndex(int index) { + return index; + } + + @Override + public Vault get(int index) { + VaultSettings s = source.get(index); + return vaultFactory.get(s); + } + + @Override + public void add(int index, Vault element) { + source.add(index, element.getVaultSettings()); + } + + @Override + public Vault remove(int index) { + VaultSettings s = source.remove(index); + return vaultFactory.get(s); + } + + @Override + public int size() { + return getSource().size(); + } + + @Override + protected void sourceChanged(Change c) { + this.fireChange(new VaultListChange(c)); + } + + private class VaultListChange extends Change { + + private final Change delegate; + + public VaultListChange(Change delegate) { + super(VaultList.this); + this.delegate = delegate; + } + + @Override + public boolean next() { + return delegate.next(); + } + + @Override + public void reset() { + delegate.reset(); + } + + @Override + public int getFrom() { + return delegate.getFrom(); + } + + @Override + public int getTo() { + return delegate.getTo(); + } + + @Override + public List getRemoved() { + List removed = new ArrayList<>(); + for (VaultSettings s : delegate.getRemoved()) { + removed.add(vaultFactory.get(s)); + } + return removed; + } + + @Override + protected int[] getPermutation() { + if (delegate.wasPermutated()) { + int len = getTo() - getFrom(); + int[] permutations = new int[len]; + for (int i = 0; i < len; i++) { + permutations[i] = getPermutation(i); + } + return permutations; + } else { + return new int[0]; + } + } + + } + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/VaultModule.java b/main/ui/src/main/java/org/cryptomator/ui/model/VaultModule.java new file mode 100644 index 000000000..c9c5f391c --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/model/VaultModule.java @@ -0,0 +1,36 @@ +package org.cryptomator.ui.model; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +import javax.inject.Scope; + +import org.cryptomator.ui.settings.VaultSettings; + +import dagger.Module; +import dagger.Provides; + +@Module +public class VaultModule { + + private final VaultSettings vaultSettings; + + public VaultModule(VaultSettings vaultSettings) { + this.vaultSettings = Objects.requireNonNull(vaultSettings); + } + + @Provides + @PerVault + public VaultSettings provideVaultSettings() { + return vaultSettings; + } + + @Scope + @Documented + @Retention(RetentionPolicy.RUNTIME) + @interface PerVault { + } + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/VaultObjectMapperProvider.java b/main/ui/src/main/java/org/cryptomator/ui/model/VaultObjectMapperProvider.java deleted file mode 100644 index 90e86abdc..000000000 --- a/main/ui/src/main/java/org/cryptomator/ui/model/VaultObjectMapperProvider.java +++ /dev/null @@ -1,97 +0,0 @@ -/******************************************************************************* - * 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.ui.model; - -import java.io.IOException; -import java.nio.file.FileSystems; -import java.nio.file.Path; - -import javax.inject.Inject; -import javax.inject.Provider; -import javax.inject.Singleton; - -import org.apache.commons.lang3.CharUtils; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.exc.InvalidFormatException; -import com.fasterxml.jackson.databind.module.SimpleModule; - -@Singleton -public class VaultObjectMapperProvider implements Provider { - - private final VaultFactory vaultFactoy; - - @Inject - public VaultObjectMapperProvider(final VaultFactory vaultFactoy) { - this.vaultFactoy = vaultFactoy; - } - - @Override - public ObjectMapper get() { - final ObjectMapper om = new ObjectMapper(); - final SimpleModule module = new SimpleModule("VaultJsonMapper"); - module.addSerializer(Vault.class, new VaultSerializer()); - module.addDeserializer(Vault.class, new VaultDeserializer()); - om.registerModule(module); - return om; - } - - private static class VaultSerializer extends JsonSerializer { - - @Override - public void serialize(Vault value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { - jgen.writeStartObject(); - jgen.writeStringField("path", value.path().getValue().toString()); - jgen.writeStringField("mountName", value.getMountName()); - jgen.writeStringField("id", value.getId()); - final Character winDriveLetter = value.getWinDriveLetter(); - if (winDriveLetter != null) { - jgen.writeStringField("winDriveLetter", Character.toString(winDriveLetter)); - } - jgen.writeEndObject(); - } - - } - - private class VaultDeserializer extends JsonDeserializer { - - @Override - public Vault deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { - final JsonNode node = jp.readValueAsTree(); - if (node == null || !node.has("path")) { - throw new InvalidFormatException("Node is null or doesn't contain a path.", node, Vault.class); - } - final String pathStr = node.get("path").asText(); - final Path path = FileSystems.getDefault().getPath(pathStr); - final Vault vault; - if (node.has("id")) { - vault = vaultFactoy.createVault(node.get("id").asText(), path); - } else { - vault = vaultFactoy.createVault(path); - } - if (node.has("mountName")) { - vault.setMountName(node.get("mountName").asText()); - } - if (node.has("winDriveLetter")) { - vault.setWinDriveLetter(CharUtils.toCharacterObject(node.get("winDriveLetter").asText())); - } - return vault; - } - - } - -} diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/Vaults.java b/main/ui/src/main/java/org/cryptomator/ui/model/Vaults.java deleted file mode 100644 index 9ac94d66e..000000000 --- a/main/ui/src/main/java/org/cryptomator/ui/model/Vaults.java +++ /dev/null @@ -1,177 +0,0 @@ -package org.cryptomator.ui.model; - -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import org.cryptomator.ui.settings.Settings; - -import javafx.beans.InvalidationListener; -import javafx.collections.FXCollections; -import javafx.collections.ListChangeListener; -import javafx.collections.ListChangeListener.Change; -import javafx.collections.ObservableList; - -@Singleton -public class Vaults implements ObservableList { - - private final ObservableList delegate; - - @Inject - public Vaults(Settings settings) { - this.delegate = FXCollections.observableList(settings.getDirectories()); - addListener((Change change) -> settings.save()); - } - - public void addListener(ListChangeListener listener) { - delegate.addListener(listener); - } - - public void removeListener(ListChangeListener listener) { - delegate.removeListener(listener); - } - - public void addListener(InvalidationListener listener) { - delegate.addListener(listener); - } - - public boolean addAll(Vault... elements) { - return delegate.addAll(elements); - } - - public boolean setAll(Vault... elements) { - return delegate.setAll(elements); - } - - public boolean setAll(Collection col) { - return delegate.setAll(col); - } - - public boolean removeAll(Vault... elements) { - return delegate.removeAll(elements); - } - - public void removeListener(InvalidationListener listener) { - delegate.removeListener(listener); - } - - public boolean retainAll(Vault... elements) { - return delegate.retainAll(elements); - } - - public void remove(int from, int to) { - delegate.remove(from, to); - } - - public int size() { - return delegate.size(); - } - - public boolean isEmpty() { - return delegate.isEmpty(); - } - - public boolean contains(Object o) { - return delegate.contains(o); - } - - public Iterator iterator() { - return delegate.iterator(); - } - - public Object[] toArray() { - return delegate.toArray(); - } - - public T[] toArray(T[] a) { - return delegate.toArray(a); - } - - public boolean add(Vault e) { - return delegate.add(e); - } - - public boolean remove(Object o) { - return delegate.remove(o); - } - - public boolean containsAll(Collection c) { - return delegate.containsAll(c); - } - - public boolean addAll(Collection c) { - return delegate.addAll(c); - } - - public boolean addAll(int index, Collection c) { - return delegate.addAll(index, c); - } - - public boolean removeAll(Collection c) { - return delegate.removeAll(c); - } - - public boolean retainAll(Collection c) { - return delegate.retainAll(c); - } - - public void clear() { - delegate.clear(); - } - - public Vault get(int index) { - return delegate.get(index); - } - - public Vault set(int index, Vault element) { - return delegate.set(index, element); - } - - public void add(int index, Vault element) { - delegate.add(index, element); - } - - public Vault remove(int index) { - return delegate.remove(index); - } - - public int indexOf(Object o) { - return delegate.indexOf(o); - } - - public int lastIndexOf(Object o) { - return delegate.lastIndexOf(o); - } - - public ListIterator listIterator() { - return delegate.listIterator(); - } - - public ListIterator listIterator(int index) { - return delegate.listIterator(index); - } - - public List subList(int fromIndex, int toIndex) { - return delegate.subList(fromIndex, toIndex); - } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (obj == null || getClass() != obj.getClass()) return false; - return internalEquals((Vaults)obj); - } - - private boolean internalEquals(Vaults other) { - return delegate.equals(other.delegate); - } - - public int hashCode() { - return delegate.hashCode(); - } - -} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WindowsDriveLetters.java b/main/ui/src/main/java/org/cryptomator/ui/model/WindowsDriveLetters.java similarity index 59% rename from main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WindowsDriveLetters.java rename to main/ui/src/main/java/org/cryptomator/ui/model/WindowsDriveLetters.java index 76ed8d71d..f6646748b 100644 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WindowsDriveLetters.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/WindowsDriveLetters.java @@ -1,18 +1,10 @@ -/******************************************************************************* - * 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.frontend.webdav.mount; - -import static java.util.stream.Collectors.toSet; +package org.cryptomator.ui.model; import java.nio.file.FileSystems; import java.nio.file.Path; import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.StreamSupport; @@ -22,8 +14,6 @@ import javax.inject.Singleton; import org.apache.commons.lang3.CharUtils; import org.apache.commons.lang3.SystemUtils; -import com.google.common.collect.Sets; - @Singleton public final class WindowsDriveLetters { @@ -31,7 +21,7 @@ public final class WindowsDriveLetters { static { try (IntStream stream = IntStream.rangeClosed('A', 'Z')) { - A_TO_Z = stream.mapToObj(i -> (char) i).collect(toSet()); + A_TO_Z = stream.mapToObj(i -> (char) i).collect(Collectors.toSet()); } } @@ -44,11 +34,13 @@ public final class WindowsDriveLetters { throw new UnsupportedOperationException("This method is only defined for Windows file systems"); } Iterable rootDirs = FileSystems.getDefault().getRootDirectories(); - return StreamSupport.stream(rootDirs.spliterator(), false).map(Path::toString).map(CharUtils::toChar).map(Character::toUpperCase).collect(toSet()); + return StreamSupport.stream(rootDirs.spliterator(), false).map(Path::toString).map(CharUtils::toChar).map(Character::toUpperCase).collect(Collectors.toSet()); } public Set getAvailableDriveLetters() { - return Sets.difference(A_TO_Z, getOccupiedDriveLetters()); + Set occupiedDriveLetters = getOccupiedDriveLetters(); + Predicate isOccupiedDriveLetter = occupiedDriveLetters::contains; + return A_TO_Z.stream().filter(isOccupiedDriveLetter.negate()).collect(Collectors.toSet()); } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/settings/Localization.java b/main/ui/src/main/java/org/cryptomator/ui/settings/Localization.java index b51382fcd..7507bf2b7 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/settings/Localization.java +++ b/main/ui/src/main/java/org/cryptomator/ui/settings/Localization.java @@ -65,8 +65,8 @@ public class Localization extends ResourceBundle { } // returns null if no resource for given path - private ResourceBundle loadLocalizationFile(String resourcePath) throws IOException { - try (InputStream in = getClass().getResourceAsStream(resourcePath)) { + private static ResourceBundle loadLocalizationFile(String resourcePath) throws IOException { + try (InputStream in = Localization.class.getResourceAsStream(resourcePath)) { if (in != null) { Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8); return new PropertyResourceBundle(reader); diff --git a/main/ui/src/main/java/org/cryptomator/ui/settings/Settings.java b/main/ui/src/main/java/org/cryptomator/ui/settings/Settings.java index e4180f969..fca6860f1 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/settings/Settings.java +++ b/main/ui/src/main/java/org/cryptomator/ui/settings/Settings.java @@ -8,20 +8,15 @@ ******************************************************************************/ package org.cryptomator.ui.settings; -import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; -import org.cryptomator.ui.model.Vault; +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; +public class Settings { -@JsonPropertyOrder(value = {"directories", "checkForUpdatesEnabled", "port", "useIpv6", "numTrayNotifications", "preferredGvfsScheme"}) -public class Settings implements Serializable { - - private static final long serialVersionUID = 7609959894417878744L; public static final int MIN_PORT = 1024; public static final int MAX_PORT = 65535; public static final int DEFAULT_PORT = 42427; @@ -32,25 +27,32 @@ public class Settings implements Serializable { private final Consumer saveCmd; - @JsonProperty("directories") - private List directories; + @Expose + @SerializedName("directories") + private List directories; - @JsonProperty("checkForUpdatesEnabled") + @Expose + @SerializedName("checkForUpdatesEnabled") private Boolean checkForUpdatesEnabled; - @JsonProperty("port") + @Expose + @SerializedName("port") private Integer port; - @JsonProperty("useIpv6") + @Expose + @SerializedName("useIpv6") private Boolean useIpv6; - @JsonProperty("numTrayNotifications") + @Expose + @SerializedName("numTrayNotifications") private Integer numTrayNotifications; - @JsonProperty("preferredGvfsScheme") + @Expose + @SerializedName("preferredGvfsScheme") private String preferredGvfsScheme; - @JsonProperty("debugMode") + @Expose + @SerializedName("debugMode") private Boolean debugMode; /** @@ -61,19 +63,21 @@ public class Settings implements Serializable { } public void save() { - saveCmd.accept(this); + if (saveCmd != null) { + saveCmd.accept(this); + } } /* Getter/Setter */ - public List getDirectories() { + public List getDirectories() { if (directories == null) { directories = new ArrayList<>(); } return directories; } - public void setDirectories(List directories) { + public void setDirectories(List directories) { this.directories = directories; } diff --git a/main/ui/src/main/java/org/cryptomator/ui/settings/SettingsInstanceCreator.java b/main/ui/src/main/java/org/cryptomator/ui/settings/SettingsInstanceCreator.java new file mode 100644 index 000000000..66367ac12 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/settings/SettingsInstanceCreator.java @@ -0,0 +1,21 @@ +package org.cryptomator.ui.settings; + +import java.lang.reflect.Type; +import java.util.function.Consumer; + +import com.google.gson.InstanceCreator; + +class SettingsInstanceCreator implements InstanceCreator { + + private final Consumer saveCmd; + + public SettingsInstanceCreator(Consumer saveCmd) { + this.saveCmd = saveCmd; + } + + @Override + public Settings createInstance(Type type) { + return new Settings(saveCmd); + } + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/settings/SettingsProvider.java b/main/ui/src/main/java/org/cryptomator/ui/settings/SettingsProvider.java index 5617815d3..8a8c94b0d 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/settings/SettingsProvider.java +++ b/main/ui/src/main/java/org/cryptomator/ui/settings/SettingsProvider.java @@ -10,7 +10,12 @@ package org.cryptomator.ui.settings; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.nio.charset.StandardCharsets; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; @@ -25,16 +30,17 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import javax.inject.Inject; -import javax.inject.Named; import javax.inject.Provider; import javax.inject.Singleton; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.LazyInitializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; @Singleton public class SettingsProvider implements Provider { @@ -54,16 +60,21 @@ public class SettingsProvider implements Provider { } } - private final ObjectMapper objectMapper; private final ScheduledExecutorService saveScheduler = Executors.newSingleThreadScheduledExecutor(); private final AtomicReference> scheduledSaveCmd = new AtomicReference<>(); + private final AtomicReference settings = new AtomicReference<>(); + private final SettingsInstanceCreator settingsInstanceCreator = new SettingsInstanceCreator(this::scheduleSave); + private final Gson gson; @Inject - public SettingsProvider(@Named("VaultJsonMapper") ObjectMapper objectMapper) { - this.objectMapper = objectMapper; + public SettingsProvider() { + GsonBuilder gsonBuilder = new GsonBuilder() // + .setPrettyPrinting().setLenient().disableHtmlEscaping().excludeFieldsWithoutExposeAnnotation(); + gsonBuilder.registerTypeAdapter(Settings.class, settingsInstanceCreator); + this.gson = gsonBuilder.create(); } - private Path getSettingsPath() throws IOException { + private Path getSettingsPath() { final String settingsPathProperty = System.getProperty("cryptomator.settingsPath"); return Optional.ofNullable(settingsPathProperty).filter(StringUtils::isNotBlank).map(this::replaceHomeDir).map(FileSystems.getDefault()::getPath).orElse(DEFAULT_SETTINGS_PATH); } @@ -78,14 +89,19 @@ public class SettingsProvider implements Provider { @Override public Settings get() { - final Settings settings = new Settings(this::scheduleSave); - try { - final Path settingsPath = getSettingsPath(); - final InputStream in = Files.newInputStream(settingsPath, StandardOpenOption.READ); - objectMapper.readerForUpdating(settings).readValue(in); + return LazyInitializer.initializeLazily(settings, this::load); + } + + private Settings load() { + Settings settings; + final Path settingsPath = getSettingsPath(); + try (InputStream in = Files.newInputStream(settingsPath, StandardOpenOption.READ); // + Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) { + settings = gson.fromJson(reader, Settings.class); LOG.info("Settings loaded from " + settingsPath); } catch (IOException e) { LOG.info("Failed to load settings, creating new one."); + settings = settingsInstanceCreator.createInstance(Settings.class); } return settings; } @@ -105,12 +121,14 @@ public class SettingsProvider implements Provider { private void save(Settings settings) { Objects.requireNonNull(settings); + final Path settingsPath = getSettingsPath(); try { - final Path settingsPath = getSettingsPath(); Files.createDirectories(settingsPath.getParent()); - final OutputStream out = Files.newOutputStream(settingsPath, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); - objectMapper.writeValue(out, settings); - LOG.info("Settings saved to " + settingsPath); + try (OutputStream out = Files.newOutputStream(settingsPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); // + Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) { + gson.toJson(settings, writer); + LOG.info("Settings saved to " + settingsPath); + } } catch (IOException e) { LOG.error("Failed to save settings.", e); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/settings/VaultSettings.java b/main/ui/src/main/java/org/cryptomator/ui/settings/VaultSettings.java new file mode 100644 index 000000000..1082cc8a3 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/settings/VaultSettings.java @@ -0,0 +1,108 @@ +package org.cryptomator.ui.settings; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.Base64; +import java.util.Objects; +import java.util.UUID; + +import com.google.gson.annotations.JsonAdapter; + +import javafx.beans.property.Property; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; + +@JsonAdapter(VaultSettingsJsonAdapter.class) +public class VaultSettings { + + private final String id; + private final Property path = new SimpleObjectProperty<>(); + private final Property mountName = new SimpleStringProperty(); + private final Property winDriveLetter = new SimpleStringProperty(); + + public VaultSettings(String id) { + this.id = Objects.requireNonNull(id); + } + + public static VaultSettings withRandomId() { + return new VaultSettings(generateId()); + } + + private static String generateId() { + return asBase64String(nineBytesFrom(UUID.randomUUID())); + } + + private static String asBase64String(byte[] bytes) { + byte[] base64Bytes = Base64.getUrlEncoder().encode(bytes); + return new String(base64Bytes, StandardCharsets.US_ASCII); + } + + private static byte[] nineBytesFrom(UUID uuid) { + ByteBuffer uuidBuffer = ByteBuffer.allocate(9); + uuidBuffer.putLong(uuid.getMostSignificantBits()); + uuidBuffer.put((byte) (uuid.getLeastSignificantBits() & 0xFF)); + uuidBuffer.flip(); + return uuidBuffer.array(); + } + + /* Getter/Setter */ + + public String getId() { + return id; + } + + public Property pathProperty() { + return path; + } + + public Path getPath() { + return path.getValue(); + } + + public void setPath(Path path) { + this.path.setValue(path); + } + + public Property mountNameProperty() { + return mountName; + } + + public String getMountName() { + return mountName.getValue(); + } + + public void setMountName(String mountName) { + this.mountName.setValue(mountName); + } + + public Property winDriveLetterProperty() { + return mountName; + } + + public String getWinDriveLetter() { + return winDriveLetter.getValue(); + } + + public void setWinDriveLetter(String winDriveLetter) { + this.winDriveLetter.setValue(winDriveLetter); + } + + /* Hashcode/Equals */ + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof VaultSettings && obj.getClass().equals(this.getClass())) { + VaultSettings other = (VaultSettings) obj; + return Objects.equals(this.id, other.id); + } else { + return false; + } + } + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/settings/VaultSettingsJsonAdapter.java b/main/ui/src/main/java/org/cryptomator/ui/settings/VaultSettingsJsonAdapter.java new file mode 100644 index 000000000..a6ed2393e --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/settings/VaultSettingsJsonAdapter.java @@ -0,0 +1,64 @@ +package org.cryptomator.ui.settings; + +import java.io.IOException; +import java.nio.file.Paths; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +class VaultSettingsJsonAdapter extends TypeAdapter { + + private static final Logger LOG = LoggerFactory.getLogger(VaultSettingsJsonAdapter.class); + + @Override + public void write(JsonWriter out, VaultSettings value) throws IOException { + out.beginObject(); + out.name("id").value(value.getId()); + out.name("path").value(value.getPath().toString()); + out.name("mountName").value(value.getMountName()); + out.name("winDriveLetter").value(value.getWinDriveLetter()); + out.endObject(); + } + + @Override + public VaultSettings read(JsonReader in) throws IOException { + String id = null; + String path = null; + String mountName = null; + String winDriveLetter = null; + + in.beginObject(); + while (in.hasNext()) { + String name = in.nextName(); + switch (name) { + case "id": + id = in.nextString(); + break; + case "path": + path = in.nextString(); + break; + case "mountName": + mountName = in.nextString(); + break; + case "winDriveLetter": + winDriveLetter = in.nextString(); + break; + default: + LOG.warn("Unsupported vault setting found in JSON: " + name); + in.skipValue(); + } + } + in.endObject(); + + VaultSettings settings = (id == null) ? VaultSettings.withRandomId() : new VaultSettings(id); + settings.setPath(Paths.get(path)); + settings.setMountName(mountName); + settings.setWinDriveLetter(winDriveLetter); + return settings; + } + +} From 4fb8a27a78b8b3358c6abd5c358d23001cd9fa2d Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 19 Dec 2016 12:06:41 +0100 Subject: [PATCH 02/17] removed MAC warning screen --- .../ui/controllers/MacWarningsController.java | 165 ------------------ .../ui/controllers/UnlockedController.java | 39 +---- .../ui/src/main/resources/localization/en.txt | 6 - 3 files changed, 1 insertion(+), 209 deletions(-) delete mode 100644 main/ui/src/main/java/org/cryptomator/ui/controllers/MacWarningsController.java diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/MacWarningsController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/MacWarningsController.java deleted file mode 100644 index cb890e1f6..000000000 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/MacWarningsController.java +++ /dev/null @@ -1,165 +0,0 @@ -/******************************************************************************* - * 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.ui.controllers; - -import java.net.URL; -import java.util.stream.Collectors; - -import javax.inject.Inject; - -import org.cryptomator.ui.model.Vault; -import org.cryptomator.ui.settings.Localization; - -import javafx.application.Application; -import javafx.beans.Observable; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.ReadOnlyStringWrapper; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; -import javafx.beans.value.WeakChangeListener; -import javafx.collections.FXCollections; -import javafx.collections.ListChangeListener; -import javafx.collections.ListChangeListener.Change; -import javafx.collections.ObservableList; -import javafx.event.ActionEvent; -import javafx.fxml.FXML; -import javafx.scene.control.Button; -import javafx.scene.control.ListView; -import javafx.scene.control.cell.CheckBoxListCell; -import javafx.stage.Stage; -import javafx.util.StringConverter; - -public class MacWarningsController extends LocalizedFXMLViewController { - - private final Application application; - private final ObservableList warnings = FXCollections.observableArrayList(); - private final ListChangeListener unauthenticatedResourcesChangeListener = this::unauthenticatedResourcesDidChange; - private final ChangeListener stageVisibilityChangeListener = this::windowVisibilityDidChange; - final ObjectProperty vault = new SimpleObjectProperty<>(); - private Stage stage; - - @Inject - public MacWarningsController(Application application, Localization localization) { - super(localization); - this.application = application; - } - - @FXML - private ListView warningsList; - - @FXML - private Button whitelistButton; - - @Override - public void initialize() { - warnings.addListener(this::warningsDidInvalidate); - warningsList.setItems(warnings); - warningsList.setCellFactory(CheckBoxListCell.forListView(Warning::selectedProperty, new StringConverter() { - - @Override - public String toString(Warning object) { - return object.getName(); - } - - @Override - public Warning fromString(String string) { - return null; - } - - })); - } - - @Override - protected URL getFxmlResourceUrl() { - return getClass().getResource("/fxml/mac_warnings.fxml"); - } - - @Override - public void initStage(Stage stage) { - super.initStage(stage); - this.stage = stage; - stage.showingProperty().addListener(new WeakChangeListener<>(stageVisibilityChangeListener)); - } - - @FXML - private void didClickWhitelistButton(ActionEvent event) { - warnings.filtered(w -> w.isSelected()).stream().forEach(w -> { - final String resourceToBeWhitelisted = w.getName(); - vault.get().getWhitelistedResourcesWithInvalidMac().add(resourceToBeWhitelisted); - vault.get().getNamesOfResourcesWithInvalidMac().remove(resourceToBeWhitelisted); - }); - warnings.removeIf(w -> w.isSelected()); - } - - @FXML - private void didClickMoreInformationButton(ActionEvent event) { - application.getHostServices().showDocument("https://cryptomator.freshdesk.com/support/solutions/articles/16000003666-what-does-mac-authentication-failed-mean-"); - } - - private void unauthenticatedResourcesDidChange(Change change) { - while (change.next()) { - if (change.wasAdded()) { - warnings.addAll(change.getAddedSubList().stream().map(Warning::new).collect(Collectors.toList())); - } else if (change.wasRemoved()) { - change.getRemoved().forEach(str -> { - warnings.removeIf(w -> str.equals(w.name.get())); - }); - } - } - } - - private void warningsDidInvalidate(Observable observable) { - disableWhitelistButtonIfNothingSelected(); - } - - private void windowVisibilityDidChange(ObservableValue observable, Boolean oldValue, Boolean newValue) { - if (Boolean.TRUE.equals(newValue)) { - stage.setTitle(String.format(localization.getString("macWarnings.windowTitle"), vault.get().name().getValue())); - warnings.addAll(vault.get().getNamesOfResourcesWithInvalidMac().stream().map(Warning::new).collect(Collectors.toList())); - vault.get().getNamesOfResourcesWithInvalidMac().addListener(this.unauthenticatedResourcesChangeListener); - } else { - vault.get().getNamesOfResourcesWithInvalidMac().clear(); - vault.get().getNamesOfResourcesWithInvalidMac().removeListener(this.unauthenticatedResourcesChangeListener); - } - } - - private void disableWhitelistButtonIfNothingSelected() { - whitelistButton.setDisable(warnings.filtered(w -> w.isSelected()).isEmpty()); - } - - private class Warning { - - private final ReadOnlyStringWrapper name = new ReadOnlyStringWrapper(); - private final BooleanProperty selected = new SimpleBooleanProperty(false); - - public Warning(String name) { - this.name.set(name); - this.selectedProperty().addListener(change -> { - disableWhitelistButtonIfNothingSelected(); - }); - } - - public String getName() { - return name.get(); - } - - public BooleanProperty selectedProperty() { - return selected; - } - - public boolean isSelected() { - return selected.get(); - } - - } - -} diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java index b160804bd..c2c578e7d 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java @@ -12,21 +12,17 @@ import java.net.URL; import java.util.Optional; import javax.inject.Inject; -import javax.inject.Provider; import org.cryptomator.ui.model.Vault; import org.cryptomator.ui.settings.Localization; -import org.cryptomator.ui.util.ActiveWindowStyleSupport; import org.cryptomator.ui.util.AsyncTaskService; import org.fxmisc.easybind.EasyBind; import javafx.animation.Animation; import javafx.animation.KeyFrame; import javafx.animation.Timeline; -import javafx.application.Platform; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; -import javafx.collections.ListChangeListener; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.fxml.FXML; @@ -42,7 +38,6 @@ import javafx.scene.control.ToggleButton; import javafx.scene.input.Clipboard; import javafx.scene.input.ClipboardContent; import javafx.stage.PopupWindow.AnchorLocation; -import javafx.stage.Stage; import javafx.util.Duration; public class UnlockedController extends LocalizedFXMLViewController { @@ -50,8 +45,6 @@ public class UnlockedController extends LocalizedFXMLViewController { private static final int IO_SAMPLING_STEPS = 100; private static final double IO_SAMPLING_INTERVAL = 0.25; - private final Stage macWarningsWindow = new Stage(); - private final MacWarningsController macWarningsController; private final AsyncTaskService asyncTaskService; private final ObjectProperty vault = new SimpleObjectProperty<>(); private Optional listener = Optional.empty(); @@ -76,19 +69,13 @@ public class UnlockedController extends LocalizedFXMLViewController { private MenuItem revealVaultMenuItem; @Inject - public UnlockedController(Localization localization, Provider macWarningsControllerProvider, AsyncTaskService asyncTaskService) { + public UnlockedController(Localization localization, AsyncTaskService asyncTaskService) { super(localization); - this.macWarningsController = macWarningsControllerProvider.get(); this.asyncTaskService = asyncTaskService; - - macWarningsController.vault.bind(this.vault); } @Override public void initialize() { - macWarningsController.initStage(macWarningsWindow); - ActiveWindowStyleSupport.startObservingFocus(macWarningsWindow); - revealVaultMenuItem.disableProperty().bind(EasyBind.map(vault, vault -> vault != null && !vault.isMounted())); EasyBind.subscribe(vault, this::vaultChanged); @@ -105,18 +92,6 @@ public class UnlockedController extends LocalizedFXMLViewController { return; } - // listen to MAC warnings as long as this vault is unlocked: - // TODO overheadhunter: reimplement eventually - /* - * final ListChangeListener macWarningsListener = this::macWarningsDidChange; - * newVault.getNamesOfResourcesWithInvalidMac().addListener(macWarningsListener); - * newVault.unlockedProperty().addListener((observable, oldValue, newValue) -> { - * if (Boolean.FALSE.equals(newValue)) { - * newVault.getNamesOfResourcesWithInvalidMac().removeListener(macWarningsListener); - * } - * }); - */ - if (!vault.get().isMounted()) { // TODO Markus Kreusch #393: hyperlink auf FAQ oder sowas? messageLabel.setText(localization.getString("unlocked.label.mountFailed")); @@ -166,18 +141,6 @@ public class UnlockedController extends LocalizedFXMLViewController { Clipboard.getSystemClipboard().setContent(clipboardContent); } - // **************************************** - // MAC Auth Warnings - // **************************************** - - private void macWarningsDidChange(ListChangeListener.Change change) { - if (change.getList().size() > 0) { - Platform.runLater(macWarningsWindow::show); - } else { - Platform.runLater(macWarningsWindow::hide); - } - } - // **************************************** // IO Graph // **************************************** diff --git a/main/ui/src/main/resources/localization/en.txt b/main/ui/src/main/resources/localization/en.txt index 64c749bb4..5036deefa 100644 --- a/main/ui/src/main/resources/localization/en.txt +++ b/main/ui/src/main/resources/localization/en.txt @@ -95,12 +95,6 @@ unlocked.label.statsEncrypted=encrypted unlocked.label.statsDecrypted=decrypted unlocked.ioGraph.yAxis.label=Throughput (MiB/s) -# mac_warnings.fxml -macWarnings.windowTitle=Danger - Corrupted file in %s -macWarnings.message=Cryptomator detected potentially malicious corruptions in the following files: -macWarnings.moreInformationButton=Learn More -macWarnings.whitelistButton=Decrypt Selected Anyway - # settings.fxml settings.version.label=Version %s settings.checkForUpdates.label=Check for Updates From 97f2cee1ae63c8839c5990156756be648a96b7a5 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 19 Dec 2016 13:57:14 +0100 Subject: [PATCH 03/17] mount name now included in servlet path --- .../java/org/cryptomator/ui/model/Vault.java | 40 ++----------------- .../ui/settings/VaultSettings.java | 29 +++++++++++++- .../ui/settings/VaultSettingsJsonAdapter.java | 2 +- .../VaultSettingsTest.java} | 14 +++---- 4 files changed, 39 insertions(+), 46 deletions(-) rename main/ui/src/test/java/org/cryptomator/ui/{model/VaultTest.java => settings/VaultSettingsTest.java} (58%) 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 4d739d44b..bb4255b54 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 @@ -14,8 +14,6 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.text.Normalizer; -import java.text.Normalizer.Form; import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; @@ -63,11 +61,6 @@ public class Vault { this.vaultSettings = vaultSettings; this.server = server; this.closer = closer; - try { - setMountName(name().getValue()); - } catch (IllegalArgumentException e) { - // mount name needs to be set by the user explicitly later - } } // ****************************************************************************** @@ -102,7 +95,7 @@ public class Vault { if (!server.isRunning()) { server.start(); } - server.startWebDavServlet(fs.getPath("/"), vaultSettings.getId()); + server.startWebDavServlet(fs.getPath("/"), vaultSettings.getId() + "/" + vaultSettings.getMountName()); } catch (IOException e) { LOG.error("Unable to provide frontend", e); } finally { @@ -215,32 +208,6 @@ public class Vault { return 0l; } - /** - * Tries to form a similar string using the regular latin alphabet. - * - * @param string - * @return a string composed of a-z, A-Z, 0-9, and _. - */ - public static String normalize(String string) { - String normalized = Normalizer.normalize(string, Form.NFD); - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < normalized.length(); i++) { - char c = normalized.charAt(i); - if (Character.isWhitespace(c)) { - if (builder.length() == 0 || builder.charAt(builder.length() - 1) != '_') { - builder.append('_'); - } - } else if (c < 127 && Character.isLetterOrDigit(c)) { - builder.append(c); - } else if (c < 127) { - if (builder.length() == 0 || builder.charAt(builder.length() - 1) != '_') { - builder.append('_'); - } - } - } - return builder.toString(); - } - public String getMountName() { return vaultSettings.getMountName(); } @@ -252,11 +219,10 @@ public class Vault { * @throws IllegalArgumentException if the name is empty after normalization */ public void setMountName(String mountName) throws IllegalArgumentException { - String normalized = normalize(mountName); - if (StringUtils.isEmpty(normalized)) { + if (StringUtils.isBlank(mountName)) { throw new IllegalArgumentException("mount name is empty"); } else { - vaultSettings.setMountName(normalized); + vaultSettings.setMountName(mountName); } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/settings/VaultSettings.java b/main/ui/src/main/java/org/cryptomator/ui/settings/VaultSettings.java index 1082cc8a3..168e6ef19 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/settings/VaultSettings.java +++ b/main/ui/src/main/java/org/cryptomator/ui/settings/VaultSettings.java @@ -7,6 +7,8 @@ import java.util.Base64; import java.util.Objects; import java.util.UUID; +import org.apache.commons.lang3.StringUtils; + import com.google.gson.annotations.JsonAdapter; import javafx.beans.property.Property; @@ -46,6 +48,28 @@ public class VaultSettings { return uuidBuffer.array(); } + /* + * visible for testing + */ + static String normalizeMountName(String mountName) { + String normalizedMountName = StringUtils.stripAccents(mountName); + StringBuilder builder = new StringBuilder(); + for (char c : normalizedMountName.toCharArray()) { + if (Character.isWhitespace(c)) { + if (builder.length() == 0 || builder.charAt(builder.length() - 1) != '_') { + builder.append('_'); + } + } else if (c < 127 && Character.isLetterOrDigit(c)) { + builder.append(c); + } else { + if (builder.length() == 0 || builder.charAt(builder.length() - 1) != '_') { + builder.append('_'); + } + } + } + return builder.toString(); + } + /* Getter/Setter */ public String getId() { @@ -62,6 +86,9 @@ public class VaultSettings { public void setPath(Path path) { this.path.setValue(path); + if (StringUtils.isBlank(getMountName())) { + setMountName(path.getFileName().toString()); + } } public Property mountNameProperty() { @@ -73,7 +100,7 @@ public class VaultSettings { } public void setMountName(String mountName) { - this.mountName.setValue(mountName); + this.mountName.setValue(normalizeMountName(mountName)); } public Property winDriveLetterProperty() { diff --git a/main/ui/src/main/java/org/cryptomator/ui/settings/VaultSettingsJsonAdapter.java b/main/ui/src/main/java/org/cryptomator/ui/settings/VaultSettingsJsonAdapter.java index a6ed2393e..bbccdc159 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/settings/VaultSettingsJsonAdapter.java +++ b/main/ui/src/main/java/org/cryptomator/ui/settings/VaultSettingsJsonAdapter.java @@ -55,8 +55,8 @@ class VaultSettingsJsonAdapter extends TypeAdapter { in.endObject(); VaultSettings settings = (id == null) ? VaultSettings.withRandomId() : new VaultSettings(id); - settings.setPath(Paths.get(path)); settings.setMountName(mountName); + settings.setPath(Paths.get(path)); settings.setWinDriveLetter(winDriveLetter); return settings; } diff --git a/main/ui/src/test/java/org/cryptomator/ui/model/VaultTest.java b/main/ui/src/test/java/org/cryptomator/ui/settings/VaultSettingsTest.java similarity index 58% rename from main/ui/src/test/java/org/cryptomator/ui/model/VaultTest.java rename to main/ui/src/test/java/org/cryptomator/ui/settings/VaultSettingsTest.java index 02860edc2..604e43ed8 100644 --- a/main/ui/src/test/java/org/cryptomator/ui/model/VaultTest.java +++ b/main/ui/src/test/java/org/cryptomator/ui/settings/VaultSettingsTest.java @@ -6,21 +6,21 @@ * Contributors: * Sebastian Stenzel - initial API and implementation *******************************************************************************/ -package org.cryptomator.ui.model; +package org.cryptomator.ui.settings; import static org.junit.Assert.assertEquals; import org.junit.Test; -public class VaultTest { +public class VaultSettingsTest { @Test public void testNormalize() throws Exception { - assertEquals("_", Vault.normalize(" ")); - assertEquals("a", Vault.normalize("ä")); - assertEquals("C", Vault.normalize("Ĉ")); - assertEquals("_", Vault.normalize(":")); - assertEquals("", Vault.normalize("汉语")); + assertEquals("_", VaultSettings.normalizeMountName(" ")); + assertEquals("a", VaultSettings.normalizeMountName("ä")); + assertEquals("C", VaultSettings.normalizeMountName("Ĉ")); + assertEquals("_", VaultSettings.normalizeMountName(":")); + assertEquals("_", VaultSettings.normalizeMountName("汉语")); } } From d6c6f177e88a5381360d3453e70345ec164b38f9 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 19 Dec 2016 13:57:30 +0100 Subject: [PATCH 04/17] code simplification --- .../ui/controllers/AbstractFXMLViewController.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/AbstractFXMLViewController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/AbstractFXMLViewController.java index fac38677b..d013cc63f 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/AbstractFXMLViewController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/AbstractFXMLViewController.java @@ -72,14 +72,12 @@ abstract class AbstractFXMLViewController implements Initializable { * @return Parent view element. */ protected final Parent loadFxml() { - return LazyInitializer.initializeLazily(fxmlRoot, () -> { - final FXMLLoader loader = createFxmlLoader(); - try { - return loader.load(); - } catch (IOException e) { - throw new IllegalStateException("Could not load FXML file from location: " + loader.getLocation(), e); - } - }); + final FXMLLoader loader = createFxmlLoader(); + try { + return LazyInitializer.initializeLazily(fxmlRoot, loader::load, IOException.class); + } catch (IOException e) { + throw new IllegalStateException("Could not load FXML file from location: " + loader.getLocation(), e); + } } /** From ed109977f8974a8f4f1bd031f660e20ffe06c8f6 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 19 Dec 2016 13:57:59 +0100 Subject: [PATCH 05/17] removed mac warning window fxml --- .../src/main/resources/fxml/mac_warnings.fxml | 34 ------------------- 1 file changed, 34 deletions(-) delete mode 100644 main/ui/src/main/resources/fxml/mac_warnings.fxml diff --git a/main/ui/src/main/resources/fxml/mac_warnings.fxml b/main/ui/src/main/resources/fxml/mac_warnings.fxml deleted file mode 100644 index 1270014dd..000000000 --- a/main/ui/src/main/resources/fxml/mac_warnings.fxml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - -