Compare commits

...

160 Commits

Author SHA1 Message Date
Sebastian Stenzel
281cfb74d9 Merge branch 'release/1.3.0-rc1'
# Conflicts:
#	main/ant-kit/pom.xml
#	main/commons-test/pom.xml
#	main/commons/pom.xml
#	main/filesystem-charsets/pom.xml
#	main/filesystem-crypto-integration-tests/pom.xml
#	main/filesystem-crypto/pom.xml
#	main/filesystem-inmemory/pom.xml
#	main/filesystem-invariants-tests/pom.xml
#	main/filesystem-nameshortening/pom.xml
#	main/filesystem-nio/pom.xml
#	main/filesystem-stats/pom.xml
#	main/frontend-api/pom.xml
#	main/frontend-webdav/pom.xml
#	main/jacoco-report/pom.xml
#	main/keychain/pom.xml
#	main/launcher/pom.xml
#	main/pom.xml
#	main/uber-jar/pom.xml
#	main/ui/pom.xml
2017-04-20 16:11:37 +02:00
Sebastian Stenzel
0d03eeb5bd Adjusted build config for 1.3.0 2017-04-20 15:02:29 +02:00
Sebastian Stenzel
36e669d729 Preparing 1.3.0-rc1 2017-04-20 14:49:32 +02:00
Sebastian Stenzel
b1f55d7cfe updated version check url [ci skip] 2017-04-19 15:25:51 +02:00
Sebastian Stenzel
9aa8c46560 Updated SemVerComparator to support pre-release versions. 2017-04-19 15:09:30 +02:00
Sebastian Stenzel
2ed00ed02d fixes #435 2017-04-19 14:02:37 +02:00
Sebastian Stenzel
5d56f5beb6 Enhanced error reporting [ci skip] 2017-04-19 00:12:10 +02:00
Sebastian Stenzel
1b937dfb06 renamed variable [ci skip] 2017-04-19 00:07:10 +02:00
Sebastian Stenzel
42be5330fe Refactored FXML loading 2017-04-19 00:06:46 +02:00
Sebastian Stenzel
be8949157f Removed unused classes 2017-04-18 13:46:25 +02:00
Sebastian Stenzel
93b2a4e07a Refactored Cryptomator UI. Extracted Launcher to its own Maven module. 2017-04-18 13:40:59 +02:00
Sebastian Stenzel
ada1195a26 Updated dependencies, which fixes #473 2017-04-04 18:00:48 +02:00
Sebastian Stenzel
93563f68e0 fixes #430 2017-03-17 16:45:20 +01:00
Sebastian Stenzel
3faa0e83cc fixes #387 2017-03-17 13:16:45 +01:00
Sebastian Stenzel
1985e2af72 fixes #452 and fixes #143 2017-03-15 12:13:34 +01:00
Sebastian Stenzel
3f03d36ad6 configurable mount + reveal during unlock, preparation for #452 and #143 (still needs a little refactoring, though) 2017-03-14 20:46:31 +01:00
Sebastian Stenzel
8359deb8eb Merge branch 'feature/cryptofs' into develop 2017-03-06 22:33:55 +01:00
Sebastian Stenzel
e1f2330f95 Updated dependencies 2017-03-06 17:08:05 +01:00
Sebastian Stenzel
87014c4db2 fixed build 2017-03-01 19:04:34 +01:00
Sebastian Stenzel
c9eb9b1938 Dependency cleanup, migrated from commons-httpclient to org.apache.httpcomponents:httpclient 2017-03-01 18:59:37 +01:00
Sebastian Stenzel
28cb2e1301 Merge branch 'develop' into feature/cryptofs
# Conflicts:
#	main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Scrypt.java
#	main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/DavFileWithRange.java
#	main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/jackrabbitservlet/FilesystemResourceFactory.java
#	main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/WebDavServerTest.java
2017-02-24 16:51:42 +01:00
Sebastian Stenzel
277999112e Updated dependencies 2017-02-24 16:48:15 +01:00
Sebastian Stenzel
a7fdf3d325 Merge pull request #454 from jordanbtucker/scrypt-typo
Fix typo in Scrypt Javadoc: `costParam` -> `blockSize`
2017-02-14 10:48:40 +01:00
Jordan Tucker
54ae332364 Fix typo in Scrypt Javadoc: costParam -> blockSize
> Cost parameter `N`, larger than 1, a power of 2 and
> less than `2^(128 * costParam / 8)`

didn't make any sense. It was meant to be

> Cost parameter `N`, larger than 1, a power of 2 and
> less than `2^(128 * blockSize / 8)`
2017-02-13 22:28:07 -08:00
Tobias Hagemann
76075ffec4 Update CONTRIBUTING.md 2017-02-10 01:52:13 +01:00
Tobias Hagemann
ed448146f7 Update CONTRIBUTING.md 2017-02-10 01:50:20 +01:00
Tobias Hagemann
638e9c1a8b Update ISSUE_TEMPLATE.md 2017-02-10 01:37:21 +01:00
Markus Kreusch
1624cffb2a Update README.md 2017-02-02 17:34:39 +01:00
Sebastian Stenzel
f391a6521d automatically save settings when chaning vault properties. fixes #446 2017-02-02 11:31:00 +01:00
Sebastian Stenzel
02ae2e7ca0 Vastly refactored settings, integrated new webdav-nio-adapter snapshot version, allow reconfiguration of IPv6 and Port settings during runtime 2017-01-31 22:30:44 +01:00
Tobias Hagemann
40bd84a09c updated contribution guide 2017-01-30 21:22:10 +01:00
Tobias Hagemann
0c0fb1c4c5 Merge branch 'release/1.2.4' 2017-01-27 11:22:14 +01:00
Tobias Hagemann
ab39bd1667 updated to version 1.2.4 2017-01-27 11:12:45 +01:00
Tobias Hagemann
b41636a208 added japanese localization, updated other localizations 2017-01-26 16:01:49 +01:00
Tobias Hagemann
0902de821a fixed unit test 2017-01-25 17:32:50 +01:00
Tobias Hagemann
8a6265658e fixed range requests 2017-01-25 15:48:21 +01:00
Sebastian Stenzel
7750a49e65 specifying masterkey filename as introduced in cryptofs 1.1.0 2017-01-20 14:01:39 +01:00
Sebastian Stenzel
b75b9781c1 implemented “change password” etc 2017-01-02 17:29:43 +01:00
Sebastian Stenzel
2687c02e31 now passing unchecked CryptoExceptions up through various closures, thus being able to catch “InvalidPassphraseException” in UI 2016-12-21 17:29:54 +01:00
Sebastian Stenzel
71b65e03d6 implemented unlock/reveal/lock 2016-12-20 20:26:17 +01:00
Sebastian Stenzel
e09ee27219 connected filesystem stats 2016-12-20 10:45:40 +01:00
Sebastian Stenzel
5a3428d9b0 cleanup 2016-12-19 17:21:33 +01:00
Sebastian Stenzel
ed109977f8 removed mac warning window fxml 2016-12-19 13:57:59 +01:00
Sebastian Stenzel
d6c6f177e8 code simplification 2016-12-19 13:57:30 +01:00
Sebastian Stenzel
97f2cee1ae mount name now included in servlet path 2016-12-19 13:57:14 +01:00
Sebastian Stenzel
4fb8a27a78 removed MAC warning screen 2016-12-19 13:55:39 +01:00
Sebastian Stenzel
79b825aaad call me THE DESTROYER!!!
first compile-clean but totally fubar version
2016-12-16 17:20:56 +01:00
Sebastian Stenzel
28fedafb59 added linux-launcher-* to RPM [ci skip] 2016-12-12 11:39:23 +01:00
Tobias Hagemann
d9bff68555 updated localization stuff
- added title text to upgrade strategy
- added texts for upgrade 4 to 5
- changed most texts to title style capitalization
2016-11-30 17:22:29 +01:00
Tobias Hagemann
cef3a5fc77 Merge branch 'release/1.2.3'
# Conflicts:
#	main/ant-kit/pom.xml
#	main/commons-test/pom.xml
#	main/commons/pom.xml
#	main/filesystem-api/pom.xml
#	main/filesystem-charsets/pom.xml
#	main/filesystem-crypto-integration-tests/pom.xml
#	main/filesystem-crypto/pom.xml
#	main/filesystem-inmemory/pom.xml
#	main/filesystem-invariants-tests/pom.xml
#	main/filesystem-nameshortening/pom.xml
#	main/filesystem-nio/pom.xml
#	main/filesystem-stats/pom.xml
#	main/frontend-api/pom.xml
#	main/frontend-webdav/pom.xml
#	main/jacoco-report/pom.xml
#	main/keychain/pom.xml
#	main/pom.xml
#	main/uber-jar/pom.xml
#	main/ui/pom.xml
2016-11-29 23:03:26 +01:00
Tobias Hagemann
9956f43fd9 updated to version 1.2.3 2016-11-29 22:24:26 +01:00
Tobias Hagemann
2b84593bde updated localization 2016-11-29 22:16:46 +01:00
Markus Kreusch
4e728fd387 Merge branch 'feature/issue-363' into develop 2016-11-29 15:21:48 +01:00
Tobias Hagemann
438ade1106 fixes #382 2016-11-27 14:28:44 +01:00
Sebastian Stenzel
fe54f4ec66 Update README.md
As suggested by @tallesh in #135
2016-11-27 10:14:34 +01:00
Markus Kreusch
fe86b4c593 Implemented #363 2016-11-14 22:26:55 +01:00
Markus Kreusch
a583afeb60 Merge branch 'feature/issue-393' into develop 2016-11-14 15:08:31 +01:00
Sebastian Stenzel
a585d3cf16 cherry picked from bac1d6f [ci skip] 2016-11-12 17:04:58 +01:00
Sebastian Stenzel
3db757193e Merge branch 'hotfix/1.2.2' 2016-11-12 17:02:04 +01:00
Sebastian Stenzel
bac1d6fd83 Updated siv-mode to 1.2.0 to be consistent with CryptoLib 2016-11-12 16:41:26 +01:00
Sebastian Stenzel
39ee8a9cde coverity issue 151831 2016-11-11 17:11:21 +01:00
Markus Kreusch
1263b3af81 fixed 'a' really bad thing in the last commit 2016-11-11 09:56:45 +01:00
Markus Kreusch
dafa29d8a3 Implemented #393 2016-11-10 22:58:45 +01:00
Sebastian Stenzel
2bc6fe89ad Merge branch 'release/1.2.1' 2016-11-10 15:23:11 +01:00
Sebastian Stenzel
8439216233 Updated version to 1.2.1 2016-11-10 15:13:28 +01:00
Sebastian Stenzel
aab616d184 Updated CryptoLib, hopefully fixes #373 2016-11-10 15:11:04 +01:00
Sebastian Stenzel
70c3a38c49 invoking UI methods on UI thread, might solve #351 2016-11-10 14:07:37 +01:00
Sebastian Stenzel
c64294ac3e Added chinese localizations, updated dutch localization 2016-11-10 13:41:09 +01:00
Markus Kreusch
82330db871 Additional logging for vault version upgrade 2016-11-09 15:54:10 +01:00
Sebastian Stenzel
c54a721f9a Merge pull request #385 from IAMtheIAM/patch-1
Update README.md
2016-11-06 21:59:16 +01:00
Sebastian Stenzel
355bbb5459 Merge branch 'develop' into patch-1 2016-11-06 21:53:28 +01:00
IAMtheIAM
63daa0f121 Update README.md
Update Readme with accurate info regarding v1.2.0
2016-11-06 03:56:56 -07:00
IAMtheIAM
50885d5c7c Update README.md 2016-11-05 16:51:16 -07:00
IAMtheIAM
4d68818ec5 Update README.md
Update features
2016-11-05 14:00:32 -07:00
IAMtheIAM
6fb20dd509 Update README.md
Added info about obfuscating file size and folder structure (two very important features that should be known!)
2016-11-05 13:54:39 -07:00
Sebastian Stenzel
2bb87dfa96 Merge branch 'release/1.2.0' into develop
# Conflicts:
#	main/ant-kit/pom.xml
#	main/commons-test/pom.xml
#	main/commons/pom.xml
#	main/filesystem-api/pom.xml
#	main/filesystem-charsets/pom.xml
#	main/filesystem-crypto-integration-tests/pom.xml
#	main/filesystem-crypto/pom.xml
#	main/filesystem-inmemory/pom.xml
#	main/filesystem-invariants-tests/pom.xml
#	main/filesystem-nameshortening/pom.xml
#	main/filesystem-nio/pom.xml
#	main/filesystem-stats/pom.xml
#	main/frontend-api/pom.xml
#	main/frontend-webdav/pom.xml
#	main/jacoco-report/pom.xml
#	main/keychain/pom.xml
#	main/pom.xml
#	main/uber-jar/pom.xml
#	main/ui/pom.xml
2016-09-19 15:12:24 +02:00
Sebastian Stenzel
3e374a927c Merge branch 'release/1.2.0'
# Conflicts:
#	main/ant-kit/pom.xml
#	main/commons-test/pom.xml
#	main/commons/pom.xml
#	main/filesystem-api/pom.xml
#	main/filesystem-charsets/pom.xml
#	main/filesystem-crypto-integration-tests/pom.xml
#	main/filesystem-crypto/pom.xml
#	main/filesystem-inmemory/pom.xml
#	main/filesystem-invariants-tests/pom.xml
#	main/filesystem-nameshortening/pom.xml
#	main/filesystem-nio/pom.xml
#	main/filesystem-stats/pom.xml
#	main/frontend-api/pom.xml
#	main/frontend-webdav/pom.xml
#	main/jacoco-report/pom.xml
#	main/pom.xml
#	main/uber-jar/pom.xml
#	main/ui/pom.xml
2016-09-19 15:10:43 +02:00
Sebastian Stenzel
84ac6d88f5 added new localization files to unit test [ci skip] 2016-09-15 23:55:37 +02:00
Sebastian Stenzel
72f6ee6477 updated localizations 2016-09-15 23:52:23 +02:00
Sebastian Stenzel
a3cfcb1131 Reject opening files when former filesize header is != -1 2016-09-15 23:26:13 +02:00
Sebastian Stenzel
d7d8d21ba4 Show warning when trying to migrate a masterkey with invalid version mac 2016-09-15 22:15:21 +02:00
Sebastian Stenzel
ef0425e2b1 fixes coverity issue 147409 2016-09-15 14:15:33 +02:00
Sebastian Stenzel
df1fd6d0b3 fixed coverity issue 72979 2016-09-15 14:15:25 +02:00
Sebastian Stenzel
2fa04d7b7c increased version to 1.3.0-SNAPSHOT
[ci skip]
2016-09-15 13:35:22 +02:00
Sebastian Stenzel
a15acd64c8 set version to 1.2.0 2016-09-15 13:33:37 +02:00
Sebastian Stenzel
5b18eff01a increased cryptolib version to 1.0.2
[ci skip]
2016-09-15 13:28:20 +02:00
Sebastian Stenzel
47133c6f31 fixed change pw function leaving invalid JSON file if length gets shorter due to different encoding or pretty printing etc 2016-09-14 17:22:26 +02:00
Sebastian Stenzel
09ba4f5129 changed to jni lib version 1.0.0 2016-09-13 20:17:30 +02:00
Sebastian Stenzel
20d4047bed changed to cryptolib version 1.0.1 (which includes sources) [ci skip] 2016-09-12 23:16:17 +02:00
Sebastian Stenzel
56b71ef7d9 depends on relase version of cryptolib 1.0.0 2016-09-12 21:28:21 +02:00
Sebastian Stenzel
091e62057d Injecting CryptorProvider into UpgradeStrategies 2016-09-12 13:56:47 +02:00
Sebastian Stenzel
824bd9ea64 just added a comment [ci skip] 2016-09-08 18:41:36 +02:00
Sebastian Stenzel
697a791593 updated travis config 2016-09-08 18:34:28 +02:00
Sebastian Stenzel
7462a887b3 updated travis config 2016-09-08 18:30:11 +02:00
Sebastian Stenzel
3535e83d7d updated travis config 2016-09-08 18:20:57 +02:00
Sebastian Stenzel
cf0b4accb3 Merge branch 'feature/external-keychain' into develop 2016-09-04 16:21:52 +02:00
Sebastian Stenzel
a63bcfbaa2 relaxed "vault not empty" check 2016-09-04 16:04:16 +02:00
Sebastian Stenzel
5c4bf2a207 support home-relative paths for cryptomator.keychainPath 2016-09-04 12:27:23 +02:00
Sebastian Stenzel
c1611a12ed implemented Windows keychain 2016-09-03 23:04:53 +02:00
Markus Kreusch
0983120712 Removed Syso logging 2016-09-02 19:21:54 +02:00
Sebastian Stenzel
ce12af8495 Added save password functionality to UI 2016-09-02 15:49:09 +02:00
Sebastian Stenzel
dc117c8415 oracle-java8-unlimited-jce-policy apparently no longer needed (already installed) 2016-08-31 20:12:49 +02:00
Sebastian Stenzel
06e526a961 Merge branch 'develop' into feature/external-keychain
# Conflicts:
#	main/pom.xml
2016-08-31 19:45:15 +02:00
Sebastian Stenzel
2e343a951f Feature/travis container builds (#334)
improved build dependency caching + force updates of snapshots to bypass said cache
2016-08-31 19:39:55 +02:00
Sebastian Stenzel
141ffcf656 Merge branch 'feature/native-functions' into feature/external-keychain 2016-08-31 10:41:51 +02:00
Tobias Hagemann
d61e5c5a08 added "delete passphrase" method to keychain access 2016-08-31 01:08:58 +02:00
Tobias Hagemann
6a15fa132a app launches as foreground app on mac 2016-08-30 22:41:01 +02:00
Sebastian Stenzel
902b29ee0a Merge branch 'develop' into feature/external-keychain
# Conflicts:
#	main/pom.xml
#	main/ui/src/main/java/org/cryptomator/ui/CryptomatorModule.java
2016-08-30 19:22:45 +02:00
Sebastian Stenzel
995bba616f cache maven dir 2016-08-30 19:15:26 +02:00
Sebastian Stenzel
f39b7b047f Merge branch 'feature/vaultVersion5' into develop 2016-08-30 19:14:28 +02:00
Sebastian Stenzel
72e52df4e0 implemented keychain access on OS X 2016-08-30 19:12:20 +02:00
Sebastian Stenzel
8018e9485e Merge branch 'feature/native-functions' into feature/external-keychain 2016-08-30 19:05:25 +02:00
Sebastian Stenzel
e0ae50378f externalized JNI bindings 2016-08-30 17:19:45 +02:00
Markus Kreusch
a9c2b0fc57 fixes #332 2016-08-29 21:08:58 +02:00
Sebastian Stenzel
dc58ba434a Make Cryptomator a foreground app when restoring from status bar icon 2016-08-29 20:14:48 +02:00
Sebastian Stenzel
34af306309 defined keychain access interfaces 2016-08-29 17:16:56 +02:00
Sebastian Stenzel
21d70b5ae4 moved from coveralls to codecov 2016-08-26 12:52:44 +02:00
Sebastian Stenzel
e90880ac9a speedboost 3000 2016-08-24 17:27:36 +02:00
Sebastian Stenzel
66faa13f40 unlock version 5 vaults 2016-08-23 21:35:13 +02:00
Sebastian Stenzel
8a4a29b4d1 added version 4 to 5 migrator 2016-08-23 21:15:52 +02:00
Sebastian Stenzel
8c8db84a4a refactored migration (using cryptolib) 2016-08-23 21:15:40 +02:00
Sebastian Stenzel
a499a3c80b Merge pull request #324 from oparoz/patch-1
Add that the solution works with Open Source clouds
2016-08-19 19:25:43 +02:00
Olivier Paroz
6a3ccf2b48 Add that the solution works with WebDAV clouds
I think it would be nice to promote other Open Source solutions which use standards such as WebDAV and work well with Cryptomator.
2016-08-19 19:07:39 +02:00
Tobias Hagemann
fcfcffe9cb updated tray icons for OS X [ci skip] 2016-08-19 16:56:04 +02:00
Sebastian Stenzel
363ed4ac4b Accept paths beginning with "~" in cryptomator.settingsPath JVM arg.
[ci skip]
2016-08-17 18:52:08 +02:00
Tobias Hagemann
1f73a08e09 added confirmation checkbox to upgrade screen [ci skip] 2016-08-17 18:11:27 +02:00
Sebastian Stenzel
fe0a34907f Simplified settings/log file path handling. Removed support for %appdata%. Use ~/AppData/Roaming instead! 2016-08-17 15:59:36 +02:00
Sebastian Stenzel
461b11700f added new upgrade log path setting to build script [ci skip] 2016-08-16 19:15:23 +02:00
Sebastian Stenzel
24bfbb59a4 fixes #310 2016-08-16 19:07:05 +02:00
Sebastian Stenzel
4476558e9c fixes #321 2016-08-16 12:33:15 +02:00
Sebastian Stenzel
560171832c Merge branch 'release/1.1.4'
Fixes #308, fixes #319, fixes #318, fixes #317, fixes #311, fixes #267

# Conflicts:
#	main/ant-kit/pom.xml
#	main/commons-test/pom.xml
#	main/commons/pom.xml
#	main/filesystem-api/pom.xml
#	main/filesystem-charsets/pom.xml
#	main/filesystem-crypto-integration-tests/pom.xml
#	main/filesystem-crypto/pom.xml
#	main/filesystem-inmemory/pom.xml
#	main/filesystem-invariants-tests/pom.xml
#	main/filesystem-nameshortening/pom.xml
#	main/filesystem-nio/pom.xml
#	main/filesystem-stats/pom.xml
#	main/frontend-api/pom.xml
#	main/frontend-webdav/pom.xml
#	main/jacoco-report/pom.xml
#	main/pom.xml
#	main/uber-jar/pom.xml
#	main/ui/pom.xml
2016-08-14 15:12:05 +02:00
Sebastian Stenzel
6e93d40e51 changed version to 1.1.4 2016-08-14 15:06:16 +02:00
Sebastian Stenzel
79b819bca6 Merge branch 'release/1.1.4' into develop 2016-08-14 15:04:44 +02:00
Sebastian Stenzel
a18c406cf0 fixed ConflictResolver 2016-08-14 14:56:44 +02:00
Sebastian Stenzel
6730a83cac fixes coverity issue 141842 2016-08-14 14:45:38 +02:00
Sebastian Stenzel
3b3ebd2196 fixes coverity issue 141838 2016-08-14 14:39:05 +02:00
Sebastian Stenzel
505b6542c7 fixes coverity issue 141844 2016-08-14 14:30:48 +02:00
Sebastian Stenzel
31368f0cba fixes coverity issue 141848 2016-08-14 14:28:52 +02:00
Sebastian Stenzel
5b5dd756b1 fixes coverity issue 141858 2016-08-14 14:27:48 +02:00
Sebastian Stenzel
f6ebbb23d1 fixes coverity issue 141860 2016-08-14 14:16:59 +02:00
Sebastian Stenzel
3f0373b08f removed xdg-utils dependencies, using gvfs-open instead of xdg-open. 2016-08-14 13:57:52 +02:00
Sebastian Stenzel
4c3c60060d Graceful unmounting on Windows and improved error handling of deferred closables. 2016-08-14 13:55:51 +02:00
Markus Kreusch
28f275c22d Requests on parent folders of valid vault urls no longer get delayed 2016-08-12 15:11:54 +02:00
Markus Kreusch
24df3c3809 GvfsMounters now use correct protocol. 2016-08-12 14:11:49 +02:00
Markus Kreusch
034a667e07 Fixed problem with sync conflict resolver. Issue #311 2016-08-11 11:19:12 +02:00
Markus Kreusch
008e3e3b05 Continue mounting also if command fails with error code. Issue #267 2016-08-11 09:39:38 +02:00
Markus Kreusch
94a5bf7596 Continue mounting if reg.exe command fails. Issue #267 2016-08-10 13:57:23 +02:00
Markus Kreusch
e8db836eff Workaround for Issue #317 2016-08-10 13:53:04 +02:00
Markus Kreusch
429b26f3d8 Added delay for requests on invalid vault ids. Issue #319 2016-08-10 13:44:26 +02:00
Markus Kreusch
3ae8327300 Added unique id to vaults / webdav urls.
Issue #319, Issue #308
2016-08-10 13:43:46 +02:00
Markus Kreusch
df7e9a0af1 Preventing post requests. Issue #319 2016-08-09 20:02:06 +02:00
Sebastian Stenzel
93d3eca0ab Yet another header to prevent browsers from guessing mime types. Kudos to @LukasReschke 2016-08-09 17:42:24 +02:00
Sebastian Stenzel
7753d1f0e7 If GET request is made by a browser, the file in question is downloaded instead of being executed. Issue #318 2016-08-09 17:35:17 +02:00
Sebastian Stenzel
d7c6c24932 updated tavis config 2016-08-01 10:21:10 +02:00
Sebastian Stenzel
1a75f23081 Merge branch 'feature/mount-method-settings' into develop 2016-08-01 10:17:44 +02:00
Sebastian Stenzel
f071efe1b9 allow user to specify whether to use dav:// or webdav:// scheme for Linux GVFS mounts. Fixes #307 2016-07-25 10:08:21 +02:00
Sebastian Stenzel
a8ad335aed Update README.md
[ci skip]
2016-07-18 12:18:47 +02:00
Markus Kreusch
7022a80c95 Improved error handling
* Created AsyncTaskService to build async UI operations which always log
uncaught exceptions
* Changed all executor service invocations in the UI to invocations of
AsyncTaskService
* Improved error handling in some other places, especially
try-with-resources
* Unlocking read/write locks in NioFile when opening of a channel fails
2016-07-14 13:58:17 +02:00
Sebastian Stenzel
9a2f602d6c fixes #270 2016-07-13 12:37:40 +02:00
Sebastian Stenzel
c78a4aa241 updated travis config [ci skip] 2016-07-11 22:14:35 +02:00
423 changed files with 5643 additions and 27486 deletions

View File

@@ -1,23 +1,30 @@
sudo: required
dist: trusty
language: java
sudo: required
dist: trusty
jdk:
- oraclejdk8
cache:
directories:
- $HOME/.m2
env:
global:
- secure: "Lgj042RD0X3rB8VZVZLWP1GetLhjd3PqI5JbJMlzgHJpDI6RkFIBLN9SWAGmkLPCehIp2zA5tu9+UVy0NNMxm9xz6SyjMCaxS28/fnYEXaNmwwDSF6O6gLUbdxyzoYIFPYOPmFxpzhebqnNIsxaM29oZpgRgUGqosCczQxiB+Ng=" #coveralls
- secure: "IfYURwZaDWuBDvyn47n0k1Zod/IQw1FF+CS5nnV08Q+NfC3vGGJMwV8m59XnbfwnWGxwvCaAbk4qP6s6+ijgZNKkvgfFMo3rfTok5zt43bIqgaFOANYV+OC/1c59gYD6ZUxhW5iNgMgU3qdsRtJuwSmfkVv/jKyLGfAbS4kN8BA=" #coverity
before_install: "curl -L --cookie 'oraclelicense=accept-securebackup-cookie;' http://download.oracle.com/otn-pub/java/jce/8/jce_policy-8.zip -o /tmp/policy.zip && sudo unzip -j -o /tmp/policy.zip *.jar -d `jdk_switcher home oraclejdk8`/jre/lib/security && rm /tmp/policy.zip"
script: mvn -fmain/pom.xml clean test
after_success: mvn -fmain/pom.xml -Ptest-coverage clean test jacoco:report-aggregate coveralls:report
- secure: "lV9OwUbHMrMpLUH1CY+Z4puLDdFXytudyPlG1eGRsesdpuG6KM3uQVz6uAtf6lrU8DRbMM/T7ML+PmvQ4UoPPYLdLxESLLBat2qUPOIVBOhTSlCc7I0DmGy04CSvkeMy8dPaQC0ukgNiR7zwoNzfcpGRN/U9S8tziDruuHoZSrg=" #bintray
addons:
coverity_scan:
project:
name: "cryptomator/cryptomator"
notification_email: sebastian.stenzel@cryptomator.org
build_command: "mvn -fmain/pom.xml clean test -DskipTests"
branch_pattern: release.*
install:
# "clean" needed until https://bugs.openjdk.java.net/browse/JDK-8067747 is resolved.
- mvn -fmain/pom.xml clean package -DskipTests dependency:go-offline -Ptest-coverage
- mvn -fmain/pom.xml clean package -DskipTests dependency:go-offline -Prelease
script:
- mvn --update-snapshots -fmain/pom.xml -Ptest-coverage clean test jacoco:report-aggregate
after_success:
- "bash <(curl -s https://codecov.io/bash)"
notifications:
webhooks:
urls:
@@ -30,19 +37,10 @@ notifications:
secure: "lngJ/HEAFBbD5AdiO9avMqptKpZHdmEwOzS9FabZjkdFh7yAYueTk5RniPUvShjsKtThYm7cJ8AtDMDwc07NvPrzbMBRtUJGwuDT+7c7YFALGFJ1NYi+emkC9x1oafvmPgEYSE+tMKzNcwrHi3ytGgKdIotsKwaF35QNXYA9aMs="
on_success: change
on_failure: always
before_deploy: mvn -fmain/pom.xml -Prelease clean package -DskipTests
addons:
coverity_scan:
project:
name: "cryptomator/cryptomator"
notification_email: sebastian.stenzel@cryptomator.org
build_command: "mvn -fmain/pom.xml clean test -DskipTests"
branch_pattern: release.*
before_deploy:
- mvn -fmain/pom.xml -Prelease clean package -DskipTests
deploy:
provider: releases
- provider: releases
prerelease: false
api_key:
secure: "ZjE1j93v3qbPIe2YbmhS319aCbMdLQw0HuymmluTurxXsZtn9D4t2+eTr99vBVxGRuB5lzzGezPR5zjk5W7iHF7xhwrawXrFzr2rPJWzWFt0aM+Ry2njU1ROTGGXGTbv4anWeBlgMxLEInTAy/9ytOGNJlec83yc0THpOY2wxnk="
@@ -53,3 +51,13 @@ deploy:
on:
repo: cryptomator/cryptomator
tags: true
- provider: script
script: "curl -X POST -u cryptobot:${BINTRAY_API_KEY} -H 'Content-Type: application/json' -d '{\"name\": \"${TRAVIS_TAG}\", \"vcs_tag\": \"${TRAVIS_TAG}\"}' https://api.bintray.com/packages/cryptomator/cryptomator/cryptomator-win/versions"
on:
repo: cryptomator/cryptomator
tags: true
- provider: script
script: "curl -X POST -u cryptobot:${BINTRAY_API_KEY} -H 'Content-Type: application/json' -d '{\"name\": \"${TRAVIS_TAG}\", \"vcs_tag\": \"${TRAVIS_TAG}\"}' https://api.bintray.com/packages/cryptomator/cryptomator/cryptomator-osx/versions"
on:
repo: cryptomator/cryptomator
tags: true

View File

@@ -3,8 +3,8 @@
## Did you find a bug?
- Ensure you're running the latest version of Cryptomator.
- Ensure the bug is related to the desktop version of Cryptomator. Bugs concerning the Cryptomator iOS app can be reported on the [Cryptomator for iOS issues list](https://github.com/cryptomator/cryptomator-ios/issues).
- Ensure the bug was not [already reported](https://github.com/cryptomator/cryptomator/issues). You can also check out our [FAQ](https://cryptomator.org/faq/) and our [Wiki](https://github.com/cryptomator/cryptomator/wiki).
- Ensure the bug is related to the desktop version of Cryptomator. Bugs concerning the Cryptomator iOS and Android app can be reported on the [Cryptomator for iOS issues list](https://github.com/cryptomator/cryptomator-ios/issues) and [Cryptomator for Android issues list](https://github.com/cryptomator/cryptomator-android/issues) respectively.
- Ensure the bug was not [already reported](https://github.com/cryptomator/cryptomator/issues). You can also check out our [knowledge base](https://cryptomator.freshdesk.com/support/solutions) and our [Wiki](https://github.com/cryptomator/cryptomator/wiki).
- If you're unable to find an open issue addressing the problem, [submit a new one](https://github.com/cryptomator/cryptomator/issues/new).
## Do you have questions?
@@ -13,6 +13,11 @@
- [Contact us](https://cryptomator.org/contact/) directly by writing an email. Wir sprechen auch Deutsch!
- Have a chat with us on [Gitter](https://gitter.im/cryptomator/cryptomator).
## Do you miss a feature?
- Ensure the feature was not [already requested](https://github.com/cryptomator/cryptomator/issues).
- You're welcome to suggest a feature by [submitting a new issue](https://github.com/cryptomator/cryptomator/issues/new).
## Did you write a patch that fixes a bug?
- Open a new pull request with the patch.

View File

@@ -1,19 +1,38 @@
### Basic Info
To tick a checkbox replace [ ] with [x]. Make sure to replace placeholders (…) accordingly.
- I'm running Cryptomator on: [Windows, OS X, and/or Debian (or other Linux Distribution), don't forget the version]
- I'm using Cryptomator in version: [you can check the version in the settings of Cryptomator]
## Issue Checklist
### Description
Before creating a new issue make sure that you
- [ ] searched [existing (and closed) issues](https://github.com/cryptomator/cryptomator/issues).
- [ ] searched the [knowledge base](https://cryptomator.freshdesk.com/support/solutions).
- [ ] have read the [contribution guide](https://github.com/cryptomator/cryptomator/blob/master/CONTRIBUTING.md).
- [ ] have read the [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/master/CODE_OF_CONDUCT.md).
[description of the bug, question or feature - what did you do? what problem occurred? etc.]
## Basic Info
### Log File (optional)
This is a
- [ ] bug report.
- [ ] feature request.
- [ ] question or something else.
```
[insert relevant parts of the log file here if applicable,
don't forget to redact sensitive information
I'm using
- [ ] Windows in version: …
- [ ] macOS in version: …
- [ ] Linux in version: …
on Windows: %appdata%/Cryptomator/cryptomator.log
on OS X: ~/Library/Logs/Cryptomator/cryptomator.log
on Debian: ~/.Cryptomator/cryptomator.log]
```
I'm running Cryptomator in version: …
(You can check the version in the Cryptomator settings.)
## Description
(Please describe in detail what you did, what you expected, and what really happened.)
## Attachments (optional)
If you want to add the log file or screenshots, please add them as attachments. If your log file seems empty and doesn't show any errors, you may enable the [debug mode](https://cryptomator.freshdesk.com/support/solutions/articles/16000046480) first and reproduce the problem to ensure all important information is contained in there. You may use test data or redact sensitive information from the log file.
You can find the log file
- on Windows: %appdata%/Cryptomator/cryptomator.log
- on macOS: ~/Library/Logs/Cryptomator/cryptomator.log
- on Linux: ~/.Cryptomator/cryptomator.log

View File

@@ -2,6 +2,7 @@
[![Build Status](https://travis-ci.org/cryptomator/cryptomator.svg?branch=master)](https://travis-ci.org/cryptomator/cryptomator)
[![Coverity Scan Build Status](https://scan.coverity.com/projects/cryptomator-cryptomator/badge.svg?flat=1)](https://scan.coverity.com/projects/cryptomator-cryptomator)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/2a0adf3cec6a4143b91035d3924178f1)](https://www.codacy.com/app/cryptomator/cryptomator?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=cryptomator/cryptomator&amp;utm_campaign=Badge_Grade)
[![Coverage Status](https://coveralls.io/repos/github/cryptomator/cryptomator/badge.svg?branch=master)](https://coveralls.io/github/cryptomator/cryptomator?branch=master)
[![Join the chat at https://gitter.im/cryptomator/cryptomator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/cryptomator/cryptomator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Twitter](https://img.shields.io/badge/twitter-@Cryptomator-blue.svg?style=flat)](http://twitter.com/Cryptomator)
@@ -13,12 +14,13 @@ Download native binaries of Cryptomator on [cryptomator.org](https://cryptomator
## Features
- Works with Dropbox, Google Drive, OneDrive, and any other cloud storage service that synchronizes with a local directory
- Works with Dropbox, Google Drive, OneDrive, Nextcloud and any other cloud storage service which synchronizes with a local directory
- Open Source means: No backdoors, control is better than trust
- Client-side: No accounts, no data shared with any online service
- Totally transparent: Just work on the virtual drive as if it were a USB flash drive
- AES encryption with 256-bit key length
- Filenames get encrypted, too
- File names get encrypted
- Folder structure gets obfuscated
- Use as many vaults in your Dropbox as you want, each having individual passwords
### Privacy
@@ -43,7 +45,8 @@ For more information on the security details visit [cryptomator.org](https://cry
### Dependencies
* Java 8 + JCE unlimited strength policy files (needed for 256-bit keys)
* Java 8 (min. 8u51, we recommend to use the current version)
* [JCE unlimited strength policy files](http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html) (needed for 256-bit keys)
* Maven 3
* Optional: OS-dependent build tools for native packaging (see [Windows](https://github.com/cryptomator/cryptomator-win), [OS X](https://github.com/cryptomator/cryptomator-osx), [Linux](https://github.com/cryptomator/builder-containers))
@@ -51,9 +54,11 @@ For more information on the security details visit [cryptomator.org](https://cry
```
cd main
mvn clean install
mvn clean install -Prelease
```
An executable jar file will be created inside `main/uber-jar/target`.
## Contributing to Cryptomator
Please read our [contribution guide](https://github.com/cryptomator/cryptomator/blob/master/CONTRIBUTING.md), if you would like to report a bug, ask a question or help us with coding.

View File

@@ -8,7 +8,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.1.3</version>
<version>1.3.0-rc1</version>
</parent>
<artifactId>ant-kit</artifactId>
<packaging>pom</packaging>
@@ -18,7 +18,7 @@
<dependencies>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>ui</artifactId>
<artifactId>launcher</artifactId>
</dependency>
</dependencies>
@@ -44,7 +44,7 @@
<!-- copy resources to target/: -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>2.7</version>
<version>3.0.2</version>
<executions>
<execution>
<id>copy-resources</id>
@@ -79,8 +79,8 @@
<!-- create antkit.tar.gz: -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>make-assembly</id>

View File

@@ -3,15 +3,15 @@
<taskdef uri="javafx:com.sun.javafx.tools.ant" resource="com/sun/javafx/tools/ant/antlib.xml" classpath="\${java.class.path}:\${java.home}/../lib/ant-javafx.jar:." />
<!-- Define application to build -->
<fx:application id="Cryptomator" name="Cryptomator" version="${project.version}" mainClass="org.cryptomator.ui.Cryptomator" />
<fx:application id="Cryptomator" name="Cryptomator" version="${project.version}" mainClass="org.cryptomator.launcher.Cryptomator" />
<!-- Create main application jar -->
<target name="create-jar">
<fx:jar destfile="antbuild/Cryptomator-${project.version}.jar">
<fx:application refid="Cryptomator" />
<fx:fileset dir="libs" includes="ui-${project.version}.jar" />
<fx:fileset dir="libs" includes="launcher-${project.version}.jar" />
<fx:resources>
<fx:fileset dir="libs" type="jar" includes="*.jar" excludes="ui-${project.version}.jar" />
<fx:fileset dir="libs" type="jar" includes="*.jar" excludes="launcher-${project.version}.jar" />
</fx:resources>
<fx:manifest>
<fx:attribute name="Implementation-Vendor" value="cryptomator.org" />
@@ -21,21 +21,6 @@
</fx:jar>
</target>
<!-- Create native image -->
<target name="create-linux-image-with-jvm" depends="create-jar">
<fx:deploy nativeBundles="image" outdir="antbuild" outfile="Cryptomator-${project.version}" verbose="true">
<fx:application refid="Cryptomator" />
<fx:platform j2se="8.0">
<fx:property name="cryptomator.logPath" value="~/.Cryptomator/cryptomator.log" />
<fx:jvmarg value="-Xmx512m"/>
</fx:platform>
<fx:resources>
<fx:fileset dir="antbuild" type="jar" includes="Cryptomator-${project.version}.jar" />
<fx:fileset dir="libs" type="jar" includes="*.jar" excludes="ui-${project.version}.jar"/>
</fx:resources>
</fx:deploy>
</target>
<!-- Create Debian package -->
<target name="deb" depends="create-jar">
<fx:deploy nativeBundles="deb" outdir="antbuild" outfile="Cryptomator-${project.version}" verbose="true">
@@ -45,11 +30,14 @@
</fx:info>
<fx:platform j2se="8.0">
<fx:property name="cryptomator.logPath" value="~/.Cryptomator/cryptomator.log" />
<fx:jvmarg value="-Xmx1048m"/>
<fx:property name="cryptomator.upgradeLogPath" value="~/.Cryptomator/upgrade.log" />
<fx:property name="cryptomator.settingsPath" value="~/.Cryptomator/settings.json" />
<fx:property name="cryptomator.ipcPortPath" value="~/.Cryptomator/ipcPort.bin" />
<fx:jvmarg value="-Xmx512m"/>
</fx:platform>
<fx:resources>
<fx:fileset dir="antbuild" type="jar" includes="Cryptomator-${project.version}.jar" />
<fx:fileset dir="libs" type="jar" includes="*.jar" excludes="ui-${project.version}.jar"/>
<fx:fileset dir="libs" type="jar" includes="*.jar" excludes="launcher-${project.version}.jar"/>
<fx:fileset dir="fixed-binaries" type="data" includes="linux-launcher-*" arch=""/>
</fx:resources>
<fx:permissions elevated="false" />
@@ -66,11 +54,15 @@
</fx:info>
<fx:platform j2se="8.0">
<fx:property name="cryptomator.logPath" value="~/.Cryptomator/cryptomator.log" />
<fx:jvmarg value="-Xmx1048m"/>
<fx:property name="cryptomator.upgradeLogPath" value="~/.Cryptomator/upgrade.log" />
<fx:property name="cryptomator.settingsPath" value="~/.Cryptomator/settings.json" />
<fx:property name="cryptomator.ipcPortPath" value="~/.Cryptomator/ipcPort.bin" />
<fx:jvmarg value="-Xmx512m"/>
</fx:platform>
<fx:resources>
<fx:fileset dir="antbuild" type="jar" includes="Cryptomator-${project.version}.jar" />
<fx:fileset dir="libs" type="jar" includes="*.jar" excludes="ui-${project.version}.jar"/>
<fx:fileset dir="libs" type="jar" includes="*.jar" excludes="launcher-${project.version}.jar"/>
<fx:fileset dir="fixed-binaries" type="data" includes="linux-launcher-*" arch=""/>
</fx:resources>
<fx:permissions elevated="false" />
<fx:preferences install="true" />

View File

@@ -9,7 +9,7 @@ Priority: optional
Architecture: APPLICATION_ARCH
Provides: APPLICATION_PACKAGE
Installed-Size: APPLICATION_INSTALLED_SIZE
Depends: gvfs-bin, gvfs-backends, gvfs-fuse, xdg-utils
Depends: gvfs-bin, gvfs-backends, gvfs-fuse
Description: Multi-platform client-side encryption of your cloud files.
Cryptomator provides free client-side AES encryption for your cloud files.
Create encrypted vaults, which get mounted as virtual volumes. Whatever

View File

@@ -1 +0,0 @@
/target/

View File

@@ -10,7 +10,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.1.3</version>
<version>1.2.4</version>
</parent>
<artifactId>commons-test</artifactId>
<name>Cryptomator common test dependencies</name>

View File

@@ -1,73 +0,0 @@
package org.cryptomator.common.test;
import static java.nio.file.Files.walkFileTree;
import static java.util.Collections.synchronizedSet;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashSet;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TempFilesRemovedOnShutdown {
private static final Logger LOG = LoggerFactory.getLogger(TempFilesRemovedOnShutdown.class);
private static final Set<Path> PATHS_TO_REMOVE_ON_SHUTDOWN = synchronizedSet(new HashSet<>());
private static final Thread ON_SHUTDOWN_DELETER = new Thread(TempFilesRemovedOnShutdown::removeAll);
static {
Runtime.getRuntime().addShutdownHook(ON_SHUTDOWN_DELETER);
}
public static Path createTempDirectory(String prefix) throws IOException {
Path path = Files.createTempDirectory(prefix);
PATHS_TO_REMOVE_ON_SHUTDOWN.add(path);
return path;
}
private static void removeAll() {
PATHS_TO_REMOVE_ON_SHUTDOWN.forEach(TempFilesRemovedOnShutdown::remove);
}
private static void remove(Path path) {
try {
tryRemove(path);
} catch (Throwable e) {
LOG.debug("Failed to remove " + path, e);
}
}
private static void tryRemove(Path path) throws IOException {
walkFileTree(path, new FileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
}
}

View File

@@ -1,27 +0,0 @@
package org.cryptomator.common.test.matcher;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
/**
* Wraps hamcrest contains and containsInAnyOrder matcher factory methods to
* avoid problems due to incorrect / inconsistent handling of generics by
* several java compilers.
*
* @author Markus Kreusch
*/
public class ContainsMatcher {
@SuppressWarnings({ "unchecked" })
@SafeVarargs
public static <T> Matcher<Iterable<? super T>> containsInAnyOrder(Matcher<? extends T>... matchers) {
return Matchers.containsInAnyOrder((Matcher[]) matchers);
}
@SuppressWarnings({ "unchecked" })
@SafeVarargs
public static <T> Matcher<Iterable<? super T>> contains(Matcher<? extends T>... matchers) {
return Matchers.contains((Matcher[]) matchers);
}
}

View File

@@ -1,48 +0,0 @@
package org.cryptomator.common.test.matcher;
import java.util.Optional;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeDiagnosingMatcher;
public class ExceptionMatcher<T extends Throwable> extends TypeSafeDiagnosingMatcher<T> {
public static <T extends Throwable> ExceptionMatcher<T> ofType(Class<T> exceptionType) {
return new ExceptionMatcher<>(exceptionType);
}
private final Class<T> exceptionType;
private final Optional<Matcher<T>> subMatcher;
private ExceptionMatcher(Class<T> exceptionType) {
super(exceptionType);
this.exceptionType = exceptionType;
this.subMatcher = Optional.empty();
}
private ExceptionMatcher(Class<T> exceptionType, Matcher<T> subMatcher) {
super(exceptionType);
this.exceptionType = exceptionType;
this.subMatcher = Optional.of(subMatcher);
}
@Override
public void describeTo(Description description) {
subMatcher.ifPresent(description::appendDescriptionOf);
}
@Override
protected boolean matchesSafely(T item, Description mismatchDescription) {
if (subMatcher.map(matcher -> !matcher.matches(item)).orElse(false)) {
subMatcher.get().describeMismatch(item, mismatchDescription);
return false;
}
return true;
}
public Matcher<T> withCauseThat(Matcher<? super Throwable> matcher) {
return new ExceptionMatcher<T>(exceptionType, new PropertyMatcher<>(exceptionType, Throwable::getCause, "cause", matcher));
}
}

View File

@@ -1,61 +0,0 @@
package org.cryptomator.common.test.matcher;
import java.util.Optional;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeDiagnosingMatcher;
public class OptionalMatcher {
public static <T> Matcher<Optional<T>> presentOptionalWithValueThat(Matcher<? super T> valueMatcher) {
return new TypeSafeDiagnosingMatcher<Optional<T>>(Optional.class) {
@Override
public void describeTo(Description description) {
description //
.appendText("a present Optional with a value that ") //
.appendDescriptionOf(valueMatcher);
}
@Override
protected boolean matchesSafely(Optional<T> item, Description mismatchDescription) {
if (item.isPresent()) {
if (valueMatcher.matches(item.get())) {
return true;
} else {
mismatchDescription.appendText("a present Optional with value that ");
valueMatcher.describeMismatch(item, mismatchDescription);
return false;
}
} else {
mismatchDescription.appendText("an empty Optional");
return false;
}
}
};
}
public static <T> Matcher<Optional<T>> emptyOptional() {
return new TypeSafeDiagnosingMatcher<Optional<T>>(Optional.class) {
@Override
public void describeTo(Description description) {
description.appendText("an empty Optional");
}
@Override
protected boolean matchesSafely(Optional<T> item, Description mismatchDescription) {
if (item.isPresent()) {
mismatchDescription.appendText("a present Optional of ").appendValue(item.get());
return false;
} else {
return true;
}
}
};
}
}

View File

@@ -1,55 +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.common.test.matcher;
import java.util.function.Function;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeDiagnosingMatcher;
public class PropertyMatcher<T, P> extends TypeSafeDiagnosingMatcher<T> {
private final Class<T> expectedType;
private final Function<? super T, P> getter;
private final String name;
private final Matcher<? super P> subMatcher;
public PropertyMatcher(Class<T> type, Function<? super T, P> getter, String name, Matcher<? super P> subMatcher) {
super(type);
this.expectedType = type;
this.getter = getter;
this.name = name;
this.subMatcher = subMatcher;
}
@Override
public void describeTo(Description description) {
description.appendText("a ") //
.appendText(expectedType.getSimpleName()) //
.appendText(" with a ") //
.appendText(name) //
.appendText(" that ") //
.appendDescriptionOf(subMatcher);
}
@Override
protected boolean matchesSafely(T item, Description mismatchDescription) {
P propertyValue = getter.apply(item);
if (subMatcher.matches(propertyValue)) {
return true;
} else {
mismatchDescription.appendText("a ") //
.appendText(expectedType.getSimpleName()) //
.appendText(" with a ") //
.appendText(name) //
.appendText(" that ");
subMatcher.describeMismatch(propertyValue, mismatchDescription);
return false;
}
}
}

View File

@@ -1,61 +0,0 @@
package org.cryptomator.common.test.mockito;
import static java.util.Arrays.asList;
import java.util.function.Consumer;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
public class Answers {
public static <T> Answer<T> collectParameters(Answer<T> answer, Consumer<?>... parameterConsumers) {
return new Answer<T>() {
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public T answer(InvocationOnMock invocation) throws Throwable {
for (int i = 0; i < invocation.getArguments().length; i++) {
if (parameterConsumers.length > i) {
((Consumer) parameterConsumers[i]).accept(invocation.getArguments()[i]);
}
}
return answer.answer(invocation);
}
};
}
@SafeVarargs
public static <T> Answer<T> consecutiveAnswers(Answer<T>... answers) {
if (answers == null || answers.length == 0) {
throw new IllegalArgumentException("Required at least one answer");
}
if (asList(answers).contains(null)) {
throw new IllegalArgumentException("No answers must be null");
}
return new Answer<T>() {
private int nextIndex = 0;
@Override
public T answer(InvocationOnMock invocation) throws Throwable {
try {
return answers[nextIndex].answer(invocation);
} finally {
nextIndex = (nextIndex + 1) % answers.length;
}
}
};
}
public static <T> Answer<T> value(T value) {
return new Answer<T>() {
@Override
public T answer(InvocationOnMock invocation) throws Throwable {
return value;
}
};
}
}

View File

@@ -10,10 +10,10 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.1.3</version>
<version>1.3.0-rc1</version>
</parent>
<artifactId>commons</artifactId>
<name>Cryptomator common</name>
<name>Cryptomator Commons</name>
<description>Shared utilities</description>
<dependencies>
@@ -26,6 +26,14 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>org.fxmisc.easybind</groupId>
<artifactId>easybind</artifactId>
</dependency>
<!-- DI -->
<dependency>
@@ -38,25 +46,10 @@
<scope>provided</scope>
</dependency>
<!-- Test -->
<!-- Logging -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>de.bechte.junit</groupId>
<artifactId>junit-hierarchicalcontextrunner</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

View File

@@ -1,7 +1,7 @@
package org.cryptomator.common;
@FunctionalInterface
public interface ConsumerThrowingException<T, E extends Exception> {
public interface ConsumerThrowingException<T, E extends Throwable> {
void accept(T t) throws E;

View File

@@ -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 <T> Type of the value
* @param reference A reference to a maybe not yet initialized value.
@@ -17,17 +18,59 @@ public final class LazyInitializer {
* @return The initialized value
*/
public static <T> T initializeLazily(AtomicReference<T> reference, Supplier<T> factory) {
final T existingInstance = reference.get();
if (existingInstance != null) {
return existingInstance;
SupplierThrowingException<T, RuntimeException> factoryThrowingRuntimeExceptions = () -> factory.get();
return initializeLazily(reference, factoryThrowingRuntimeExceptions, RuntimeException.class);
}
/**
* Threadsafe lazy initialization pattern as proposed on http://stackoverflow.com/a/30247202/4014509
*
* @param <T> Type of the value
* @param <E> 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, E extends Exception> T initializeLazily(AtomicReference<T> reference, SupplierThrowingException<T, E> factory, Class<E> exceptionType) throws E {
final T existing = reference.get();
if (existing != null) {
return existing;
} else {
final T newInstance = factory.get();
if (reference.compareAndSet(null, newInstance)) {
return newInstance;
} else {
return reference.get();
try {
return reference.updateAndGet(invokeFactoryIfNull(factory));
} catch (InitializationException e) {
if (exceptionType.isInstance(e.getCause())) {
throw exceptionType.cast(e.getCause());
} else {
throw e;
}
}
}
}
private static <T, E extends Exception> UnaryOperator<T> invokeFactoryIfNull(SupplierThrowingException<T, E> factory) throws InitializationException {
return currentValue -> {
if (currentValue == null) {
try {
return factory.get();
} catch (RuntimeException e) {
throw e; // don't catch unchecked exceptions
} catch (Exception e) {
throw new InitializationException(e);
}
} else {
return currentValue;
}
};
}
private static class InitializationException extends RuntimeException {
public InitializationException(Throwable cause) {
super(cause);
}
}
}

View File

@@ -1,7 +1,7 @@
package org.cryptomator.common;
@FunctionalInterface
public interface RunnableThrowingException<T extends Exception> {
public interface RunnableThrowingException<T extends Throwable> {
void run() throws T;

View File

@@ -12,12 +12,51 @@ import java.util.Comparator;
import org.apache.commons.lang3.StringUtils;
/**
* Compares version strings according to <a href="http://semver.org/spec/v2.0.0.html">SemVer 2.0.0</a>.
*/
public class SemVerComparator implements Comparator<String> {
private static final char VERSION_SEP = '.'; // http://semver.org/spec/v2.0.0.html#spec-item-2
private static final String PRE_RELEASE_SEP = "-"; // http://semver.org/spec/v2.0.0.html#spec-item-9
private static final String BUILD_SEP = "+"; // http://semver.org/spec/v2.0.0.html#spec-item-10
@Override
public int compare(String version1, String version2) {
final String[] vComps1 = StringUtils.split(version1, '.');
final String[] vComps2 = StringUtils.split(version2, '.');
// "Build metadata SHOULD be ignored when determining version precedence.
// Thus two versions that differ only in the build metadata, have the same precedence."
String v1WithoutBuildMetadata = StringUtils.substringBefore(version1, BUILD_SEP);
String v2WithoutBuildMetadata = StringUtils.substringBefore(version2, BUILD_SEP);
if (v1WithoutBuildMetadata.equals(v2WithoutBuildMetadata)) {
return 0;
}
String v1MajorMinorPatch = StringUtils.substringBefore(v1WithoutBuildMetadata, PRE_RELEASE_SEP);
String v2MajorMinorPatch = StringUtils.substringBefore(v2WithoutBuildMetadata, PRE_RELEASE_SEP);
String v1PreReleaseVersion = StringUtils.substringAfter(v1WithoutBuildMetadata, PRE_RELEASE_SEP);
String v2PreReleaseVersion = StringUtils.substringAfter(v2WithoutBuildMetadata, PRE_RELEASE_SEP);
return compare(v1MajorMinorPatch, v1PreReleaseVersion, v2MajorMinorPatch, v2PreReleaseVersion);
}
private int compare(String v1MajorMinorPatch, String v1PreReleaseVersion, String v2MajorMinorPatch, String v2PreReleaseVersion) {
int comparisonResult = compareNumericallyThenLexicographically(v1MajorMinorPatch, v2MajorMinorPatch);
if (comparisonResult == 0) {
if (v1PreReleaseVersion.isEmpty()) {
return 1; // 1.0.0 > 1.0.0-BETA
} else if (v2PreReleaseVersion.isEmpty()) {
return -1; // 1.0.0-BETA < 1.0.0
} else {
return compareNumericallyThenLexicographically(v1PreReleaseVersion, v2PreReleaseVersion);
}
} else {
return comparisonResult;
}
}
private int compareNumericallyThenLexicographically(String version1, String version2) {
final String[] vComps1 = StringUtils.split(version1, VERSION_SEP);
final String[] vComps2 = StringUtils.split(version2, VERSION_SEP);
final int commonCompCount = Math.min(vComps1.length, vComps2.length);
for (int i = 0; i < commonCompCount; i++) {
@@ -35,7 +74,7 @@ public class SemVerComparator implements Comparator<String> {
}
}
// all in common so far? longest version string wins:
// all in common so far? longest version string is considered the higher version:
return vComps1.length - vComps2.length;
}

View File

@@ -0,0 +1,56 @@
/*******************************************************************************
* 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 implementation
*******************************************************************************/
package org.cryptomator.common;
import java.util.stream.Stream;
/**
* Utility to print stack traces while analyzing issues.
*
* @author Markus Kreusch
*/
public class StackTrace {
public static void print(String message) {
Thread thread = Thread.currentThread();
System.err.println(stackTraceFor(message, thread));
}
private static String stackTraceFor(String message, Thread thread) {
StringBuilder result = new StringBuilder();
appendMessageAndThreadName(result, message, thread);
appendStackTrace(thread, result);
return result.toString();
}
private static void appendStackTrace(Thread thread, StringBuilder result) {
Stream.of(thread.getStackTrace()) //
.skip(4) //
.forEach(stackTraceElement -> append(stackTraceElement, result));
}
private static void appendMessageAndThreadName(StringBuilder result, String message, Thread thread) {
result //
.append('[') //
.append(thread.getName()) //
.append("] ") //
.append(message);
}
private static void append(StackTraceElement stackTraceElement, StringBuilder result) {
String className = stackTraceElement.getClassName();
String methodName = stackTraceElement.getMethodName();
String fileName = stackTraceElement.getFileName();
int lineNumber = stackTraceElement.getLineNumber();
result.append('\n') //
.append(className).append(':').append(methodName) //
.append(" (").append(fileName).append(':').append(lineNumber).append(')');
}
}

View File

@@ -0,0 +1,8 @@
package org.cryptomator.common;
@FunctionalInterface
public interface SupplierThrowingException<T, E extends Throwable> {
T get() throws E;
}

View File

@@ -0,0 +1,98 @@
/*******************************************************************************
* 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.common.settings;
import java.util.function.Consumer;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
public class Settings {
public static final int MIN_PORT = 1024;
public static final int MAX_PORT = 65535;
public static final boolean DEFAULT_CHECK_FOR_UDPATES = true;
public static final int DEFAULT_PORT = 42427;
public static final boolean DEFAULT_USE_IPV6 = false;
public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
public static final String DEFAULT_GVFS_SCHEME = "dav";
public static final boolean DEFAULT_DEBUG_MODE = false;
private final Consumer<Settings> saveCmd;
private final ObservableList<VaultSettings> directories = FXCollections.observableArrayList();
private final BooleanProperty checkForUpdates = new SimpleBooleanProperty(DEFAULT_CHECK_FOR_UDPATES);
private final IntegerProperty port = new SimpleIntegerProperty(DEFAULT_PORT);
private final BooleanProperty useIpv6 = new SimpleBooleanProperty(DEFAULT_USE_IPV6);
private final IntegerProperty numTrayNotifications = new SimpleIntegerProperty(DEFAULT_NUM_TRAY_NOTIFICATIONS);
private final StringProperty preferredGvfsScheme = new SimpleStringProperty(DEFAULT_GVFS_SCHEME);
private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE);
/**
* Package-private constructor; use {@link SettingsProvider}.
*/
Settings(Consumer<Settings> saveCmd) {
this.saveCmd = saveCmd;
directories.addListener((ListChangeListener.Change<? extends VaultSettings> change) -> this.save());
checkForUpdates.addListener(this::somethingChanged);
port.addListener(this::somethingChanged);
useIpv6.addListener(this::somethingChanged);
numTrayNotifications.addListener(this::somethingChanged);
preferredGvfsScheme.addListener(this::somethingChanged);
debugMode.addListener(this::somethingChanged);
}
private void somethingChanged(ObservableValue<?> observable, Object oldValue, Object newValue) {
this.save();
}
void save() {
if (saveCmd != null) {
saveCmd.accept(this);
}
}
/* Getter/Setter */
public ObservableList<VaultSettings> getDirectories() {
return directories;
}
public BooleanProperty checkForUpdates() {
return checkForUpdates;
}
public IntegerProperty port() {
return port;
}
public BooleanProperty useIpv6() {
return useIpv6;
}
public IntegerProperty numTrayNotifications() {
return numTrayNotifications;
}
public StringProperty preferredGvfsScheme() {
return preferredGvfsScheme;
}
public BooleanProperty debugMode() {
return debugMode;
}
}

View File

@@ -0,0 +1,103 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.common.settings;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
public class SettingsJsonAdapter extends TypeAdapter<Settings> {
private static final Logger LOG = LoggerFactory.getLogger(SettingsJsonAdapter.class);
private final Consumer<Settings> saveCmd;
private final VaultSettingsJsonAdapter vaultSettingsJsonAdapter = new VaultSettingsJsonAdapter();
public SettingsJsonAdapter(Consumer<Settings> saveCmd) {
this.saveCmd = saveCmd;
}
@Override
public void write(JsonWriter out, Settings value) throws IOException {
out.beginObject();
out.name("directories");
writeVaultSettingsArray(out, value.getDirectories());
out.name("checkForUpdatesEnabled").value(value.checkForUpdates().get());
out.name("port").value(value.port().get());
out.name("useIpv6").value(value.useIpv6().get());
out.name("numTrayNotifications").value(value.numTrayNotifications().get());
out.name("preferredGvfsScheme").value(value.preferredGvfsScheme().get());
out.name("debugMode").value(value.debugMode().get());
out.endObject();
}
private void writeVaultSettingsArray(JsonWriter out, Iterable<VaultSettings> vaultSettings) throws IOException {
out.beginArray();
for (VaultSettings value : vaultSettings) {
vaultSettingsJsonAdapter.write(out, value);
}
out.endArray();
}
@Override
public Settings read(JsonReader in) throws IOException {
Settings settings = new Settings(saveCmd);
in.beginObject();
while (in.hasNext()) {
String name = in.nextName();
switch (name) {
case "directories":
settings.getDirectories().addAll(readVaultSettingsArray(in, settings));
break;
case "checkForUpdatesEnabled":
settings.checkForUpdates().set(in.nextBoolean());
break;
case "port":
settings.port().set(in.nextInt());
break;
case "useIpv6":
settings.useIpv6().set(in.nextBoolean());
break;
case "numTrayNotifications":
settings.numTrayNotifications().set(in.nextInt());
break;
case "preferredGvfsScheme":
settings.preferredGvfsScheme().set(in.nextString());
break;
case "debugMode":
settings.debugMode().set(in.nextBoolean());
break;
default:
LOG.warn("Unsupported vault setting found in JSON: " + name);
in.skipValue();
}
}
in.endObject();
return settings;
}
private List<VaultSettings> readVaultSettingsArray(JsonReader in, Settings settings) throws IOException {
List<VaultSettings> result = new ArrayList<>();
in.beginArray();
while (!JsonToken.END_ARRAY.equals(in.peek())) {
result.add(vaultSettingsJsonAdapter.read(in, settings));
}
in.endArray();
return result;
}
}

View File

@@ -0,0 +1,136 @@
/*******************************************************************************
* 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.common.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;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Optional;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.inject.Inject;
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.google.gson.Gson;
import com.google.gson.GsonBuilder;
@Singleton
public class SettingsProvider implements Provider<Settings> {
private static final Logger LOG = LoggerFactory.getLogger(SettingsProvider.class);
private static final Path DEFAULT_SETTINGS_PATH;
private static final long SAVE_DELAY_MS = 1000;
static {
final FileSystem fs = FileSystems.getDefault();
if (SystemUtils.IS_OS_WINDOWS) {
DEFAULT_SETTINGS_PATH = fs.getPath(SystemUtils.USER_HOME, "AppData/Roaming/Cryptomator/settings.json");
} else if (SystemUtils.IS_OS_MAC_OSX) {
DEFAULT_SETTINGS_PATH = fs.getPath(SystemUtils.USER_HOME, "Library/Application Support/Cryptomator/settings.json");
} else {
DEFAULT_SETTINGS_PATH = fs.getPath(SystemUtils.USER_HOME, ".Cryptomator/settings.json");
}
}
private final ScheduledExecutorService saveScheduler = Executors.newSingleThreadScheduledExecutor();
private final AtomicReference<ScheduledFuture<?>> scheduledSaveCmd = new AtomicReference<>();
private final AtomicReference<Settings> settings = new AtomicReference<>();
private final SettingsJsonAdapter settingsJsonAdapter = new SettingsJsonAdapter(this::scheduleSave);
private final Gson gson;
@Inject
public SettingsProvider() {
this.gson = new GsonBuilder() //
.setPrettyPrinting().setLenient().disableHtmlEscaping() //
.registerTypeAdapter(Settings.class, settingsJsonAdapter) //
.create();
}
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);
}
private String replaceHomeDir(String path) {
if (path.startsWith("~/")) {
return SystemUtils.USER_HOME + path.substring(1);
} else {
return path;
}
}
@Override
public Settings get() {
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 = new Settings(this::scheduleSave);
}
return settings;
}
private void scheduleSave(Settings settings) {
if (settings == null) {
return;
}
ScheduledFuture<?> saveCmd = saveScheduler.schedule(() -> {
this.save(settings);
}, SAVE_DELAY_MS, TimeUnit.MILLISECONDS);
ScheduledFuture<?> previousSaveCmd = scheduledSaveCmd.getAndSet(saveCmd);
if (previousSaveCmd != null) {
previousSaveCmd.cancel(false);
}
}
private void save(Settings settings) {
assert settings != null : "method should only be invoked by #scheduleSave, which checks for null";
final Path settingsPath = getSettingsPath();
try {
Files.createDirectories(settingsPath.getParent());
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);
}
}
}

View File

@@ -0,0 +1,140 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.common.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 org.apache.commons.lang3.StringUtils;
import org.fxmisc.easybind.EasyBind;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
public class VaultSettings {
private final Settings settings;
private final String id;
private final ObjectProperty<Path> path = new SimpleObjectProperty<>();
private final StringProperty mountName = new SimpleStringProperty();
private final StringProperty winDriveLetter = new SimpleStringProperty();
private final BooleanProperty mountAfterUnlock = new SimpleBooleanProperty();
private final BooleanProperty revealAfterMount = new SimpleBooleanProperty();
public VaultSettings(Settings settings, String id) {
this.settings = settings;
this.id = Objects.requireNonNull(id);
EasyBind.subscribe(path, this::deriveMountNameFromPath);
path.addListener(this::somethingChanged);
mountName.addListener(this::somethingChanged);
winDriveLetter.addListener(this::somethingChanged);
mountAfterUnlock.addListener(this::somethingChanged);
}
private void somethingChanged(ObservableValue<?> observable, Object oldValue, Object newValue) {
settings.save();
}
private void deriveMountNameFromPath(Path path) {
if (path != null && StringUtils.isBlank(mountName.get())) {
mountName.set(normalizeMountName(path.getFileName().toString()));
}
}
public static VaultSettings withRandomId(Settings settings) {
return new VaultSettings(settings, 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();
}
public 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() {
return id;
}
public ObjectProperty<Path> path() {
return path;
}
public StringProperty mountName() {
return mountName;
}
public StringProperty winDriveLetter() {
return winDriveLetter;
}
public BooleanProperty mountAfterUnlock() {
return mountAfterUnlock;
}
public BooleanProperty revealAfterMount() {
return revealAfterMount;
}
/* 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;
}
}
}

View File

@@ -0,0 +1,78 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.common.settings;
import java.io.IOException;
import java.nio.file.Paths;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
class VaultSettingsJsonAdapter {
private static final Logger LOG = LoggerFactory.getLogger(VaultSettingsJsonAdapter.class);
public void write(JsonWriter out, VaultSettings value) throws IOException {
out.beginObject();
out.name("id").value(value.getId());
out.name("path").value(value.path().get().toString());
out.name("mountName").value(value.mountName().get());
out.name("winDriveLetter").value(value.winDriveLetter().get());
out.name("mountAfterUnlock").value(value.mountAfterUnlock().get());
out.name("revealAfterMount").value(value.revealAfterMount().get());
out.endObject();
}
public VaultSettings read(JsonReader in, Settings settings) throws IOException {
String id = null;
String path = null;
String mountName = null;
String winDriveLetter = null;
boolean mountAfterUnlock = true;
boolean revealAfterMount = true;
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;
case "mountAfterUnlock":
mountAfterUnlock = in.nextBoolean();
break;
case "revealAfterMount":
revealAfterMount = in.nextBoolean();
break;
default:
LOG.warn("Unsupported vault setting found in JSON: " + name);
in.skipValue();
}
}
in.endObject();
VaultSettings vaultSettings = (id == null) ? VaultSettings.withRandomId(settings) : new VaultSettings(settings, id);
vaultSettings.mountName().set(mountName);
vaultSettings.path().set(Paths.get(path));
vaultSettings.winDriveLetter().set(winDriveLetter);
vaultSettings.mountAfterUnlock().set(mountAfterUnlock);
vaultSettings.revealAfterMount().set(revealAfterMount);
return vaultSettings;
}
}

View File

@@ -10,7 +10,6 @@ package org.cryptomator.common;
import java.util.Comparator;
import org.cryptomator.common.SemVerComparator;
import org.junit.Assert;
import org.junit.Test;
@@ -22,8 +21,10 @@ public class SemVerComparatorTest {
@Test
public void compareEqualVersions() {
final int comparisonResult = semVerComparator.compare("1.23.4", "1.23.4");
Assert.assertEquals(0, Integer.signum(comparisonResult));
Assert.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4")));
Assert.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-alpha")));
Assert.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4+20170101", "1.23.4+20171231")));
Assert.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4-alpha+20170101", "1.23.4-alpha+20171231")));
}
// newer versions in first argument
@@ -33,7 +34,11 @@ public class SemVerComparatorTest {
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.5", "1.23.4")));
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.24.4", "1.23.4")));
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23")));
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4a", "1.23.4")));
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4-SNAPSHOT")));
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4-56.78")));
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-beta", "1.23.4-alpha")));
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-alpha.1", "1.23.4-alpha")));
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-56.79", "1.23.4-56.78")));
}
// newer versions in second argument
@@ -43,7 +48,11 @@ public class SemVerComparatorTest {
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.5")));
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4", "1.24.4")));
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23", "1.23.4")));
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4a")));
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-SNAPSHOT", "1.23.4")));
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-56.78", "1.23.4")));
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-beta")));
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-alpha.1")));
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-56.78", "1.23.4-56.79")));
}
}

View File

@@ -9,8 +9,8 @@ import static org.mockito.Mockito.when;
import java.util.function.Function;
import org.cryptomator.common.WeakValuedCache;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
@@ -83,6 +83,7 @@ public class WeakValuedCacheTest {
assertThat(result, is(sameInstance(theValue)));
}
@Ignore
@Test
public void testCacheDoesNotPreventGarbageCollectionOfValues() {
when(loader.apply(A_KEY)).thenAnswer(this::createValueUsingMoreThanHalfTheJvmMemory);

View File

@@ -0,0 +1,40 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.common.settings;
import java.io.IOException;
import org.junit.Assert;
import org.junit.Test;
public class SettingsJsonAdapterTest {
private final SettingsJsonAdapter adapter = new SettingsJsonAdapter(this::noop);
private void noop(Settings settings) {
}
@Test
public void testDeserialize() throws IOException {
String vault1Json = "{\"id\": \"1\", \"path\": \"/vault1\", \"mountName\": \"vault1\", \"winDriveLetter\": \"X\"}";
String vault2Json = "{\"id\": \"2\", \"path\": \"/vault2\", \"mountName\": \"vault2\", \"winDriveLetter\": \"Y\"}";
String json = "{\"directories\": [" + vault1Json + "," + vault2Json + "]," //
+ "\"checkForUpdatesEnabled\": true,"//
+ "\"port\": 8080,"//
+ "\"useIpv6\": true,"//
+ "\"numTrayNotifications\": 42}";
Settings settings = adapter.fromJson(json);
Assert.assertTrue(settings.checkForUpdates().get());
Assert.assertEquals(2, settings.getDirectories().size());
Assert.assertEquals(8080, settings.port().get());
Assert.assertTrue(settings.useIpv6().get());
Assert.assertEquals(42, settings.numTrayNotifications().get());
Assert.assertEquals("dav", settings.preferredGvfsScheme().get());
}
}

View File

@@ -0,0 +1,35 @@
/*******************************************************************************
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.common.settings;
import java.io.IOException;
import java.io.StringReader;
import java.nio.file.Paths;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
import com.google.gson.stream.JsonReader;
public class VaultSettingsJsonAdapterTest {
private final VaultSettingsJsonAdapter adapter = new VaultSettingsJsonAdapter();
@Test
public void testDeserialize() throws IOException {
String json = "{\"id\": \"foo\", \"path\": \"/foo/bar\", \"mountName\": \"test\", \"winDriveLetter\": \"X\", \"shouldBeIgnored\": true}";
JsonReader jsonReader = new JsonReader(new StringReader(json));
Settings settings = Mockito.mock(Settings.class);
VaultSettings vaultSettings = adapter.read(jsonReader, settings);
Assert.assertEquals("foo", vaultSettings.getId());
Assert.assertEquals(Paths.get("/foo/bar"), vaultSettings.path().get());
Assert.assertEquals("test", vaultSettings.mountName().get());
Assert.assertEquals("X", vaultSettings.winDriveLetter().get());
}
}

View File

@@ -6,21 +6,21 @@
* Contributors:
* Sebastian Stenzel - initial API and implementation
*******************************************************************************/
package org.cryptomator.ui.model;
package org.cryptomator.common.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("汉语"));
}
}

View File

@@ -1,2 +0,0 @@
/target/
/target/

View File

@@ -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 + ")");
}
}
}

View File

@@ -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<File, Consumer<File>> filesWithOperation = new TreeMap<>();
private final Map<File, ReadableFile> readableFiles = new HashMap<>();
private final Map<File, WritableFile> 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);
}
}

View File

@@ -1,15 +0,0 @@
package org.cryptomator.filesystem;
public class Deleter {
/**
* Deletes all and only the content of a given {@link Folder} but <b>not</b> the folder itself.
*/
public static void deleteContent(Folder folder) {
if (folder.exists()) {
folder.folders().forEach(Folder::delete);
folder.files().forEach(File::delete);
}
}
}

View File

@@ -1,81 +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<File> {
static final int EOF = -1;
/**
* <p>
* Opens this file for reading.
* <p>
* 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()}.
* <br>
* A limitation to the number of {@code ReadableFiles} is in general not
* required but may be set by a specific implementation.
* <p>
* If a {@link WritableFile} for this {@code File} is open the invocation of
* this method will block regarding the specified timeout.<br>
* 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;
/**
* <p>
* Opens this file for writing.
* <p>
* If the file does not exist a new empty file is created.
* <p>
* 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()}.<br>
* In addition while a {@code WritableFile} is open no {@link ReadableFile}
* can be open and vice versa.
* <p>
* If a {@code Readable-} or {@code WritableFile} for this {@code File} is
* open the invocation of this method will block regarding the specified
* timeout.<br>
* 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 <code>destination</code>.
*/
void moveTo(File destination) throws UncheckedIOException;
}

View File

@@ -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<? extends Folder> parent() {
return Optional.empty();
}
Optional<Long> quotaUsedBytes();
Optional<Long> quotaAvailableBytes();
}

View File

@@ -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 {
/**
* <p>
* Creates a {@link Stream} over all child nodes of this {@code Folder}.
* <p>
* <b>Note:</b> 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<? extends Node> children() throws UncheckedIOException;
/**
* <p>
* Returns the child {@link Node} in this directory of type {@link File}
* with the specified name.
* <p>
* 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);
}
/**
* <p>
* Returns the child {@link Node} in this directory of type {@link Folder}
* with the specified name.
* <p>
* 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 <code>this</code> 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<? extends File> 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<? extends Folder> 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 <code>true</code> 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());
}
}
}

View File

@@ -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<Folder> beforeFolderVisitor;
private final Consumer<Folder> afterFolderVisitor;
private final Consumer<File> fileVisitor;
private final Consumer<Node> 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<Folder> beforeFolderVisitor = noOp();
private Consumer<Folder> afterFolderVisitor = noOp();
private Consumer<File> fileVisitor = noOp();
private Consumer<Node> nodeVisitor = noOp();
private int maxDepth = Integer.MAX_VALUE;
private FolderVisitorBuilder() {
}
public FolderVisitorBuilder beforeFolder(Consumer<Folder> beforeFolderVisitor) {
if (beforeFolderVisitor == null) {
throw new IllegalArgumentException("Vistior may not be null");
}
this.beforeFolderVisitor = beforeFolderVisitor;
return this;
}
public FolderVisitorBuilder afterFolder(Consumer<Folder> afterFolderVisitor) {
if (afterFolderVisitor == null) {
throw new IllegalArgumentException("Vistior may not be null");
}
this.afterFolderVisitor = afterFolderVisitor;
return this;
}
public FolderVisitorBuilder forEachFile(Consumer<File> fileVisitor) {
if (fileVisitor == null) {
throw new IllegalArgumentException("Vistior may not be null");
}
this.fileVisitor = fileVisitor;
return this;
}
public FolderVisitorBuilder forEachNode(Consumer<Node> 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 <T> Consumer<T> noOp() {
return ignoredParameter -> {
};
}
}
}

View File

@@ -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}.
* <p>
* 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<? extends Folder> parent() throws UncheckedIOException;
/**
* @return <code>true</code> if the node exists.
*/
boolean exists() throws UncheckedIOException;
/**
* <p>
* Deletes the node if it exists.
* <p>
* Does nothing if the node does not exist.
*/
void delete() throws UncheckedIOException;
/**
* <p>
* Determines the last modified date of this node.
*
* @returns the last modified date of the file
*/
Instant lastModified() throws UncheckedIOException;
/**
* <p>
* Sets the last modified date of the file.
*
* @param lastModified the time to set as creation time
*/
void setLastModified(Instant lastModified) throws UncheckedIOException;
/**
* <p>
* Determines the creation time of this node.
* <p>
* 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<Instant> creationTime() throws UncheckedIOException {
return Optional.empty();
}
/**
* <p>
* Sets the creation time of this node.
* <p>
* 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();
}
}

View File

@@ -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<File, ReadableFile> readableFiles;
private final Map<File, WritableFile> writableFiles;
public OpenFiles(Map<File, ReadableFile> readableFiles, Map<File, WritableFile> 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<ReadableFile> readableFiles, Collection<WritableFile> writableFiles) {
Iterator<? extends AutoCloseable> 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;
}
}
}

View File

@@ -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.
* <!-- @formatter:off -->
* <table>
* <thead>
* <tr>
* <th>dir</th>
* <th>path</th>
* <th>result</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td>/foo/bar</td>
* <td>foo/bar</td>
* <td>/foo/bar/foo/bar</td>
* </tr>
* <tr>
* <td>/foo/bar</td>
* <td>../baz</td>
* <td>/foo/baz</td>
* </tr>
* <tr>
* <td>/foo/bar</td>
* <td>./foo/..</td>
* <td>/foo/bar</td>
* </tr>
* <tr>
* <td>/foo/bar</td>
* <td>/</td>
* <td>/foo/bar</td>
* </tr>
* <tr>
* <td>/foo/bar</td>
* <td></td>
* <td>/foo/bar</td>
* </tr>
* <tr>
* <td>/foo/bar</td>
* <td>../../..</td>
* <td>Exception</td>
* </tr>
* </tbody>
* </table>
*
* @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<String> 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);
}
}
}

View File

@@ -1,57 +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 {
/**
* <p>
* Tries to fill the remaining space in the given byte buffer with data from
* this readable bytes from the current position.
* <p>
* 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;
/**
* @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;
/**
* <p>
* Fast-forwards or rewinds the file to the specified position.
* <p>
* 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;
}

View File

@@ -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;
/**
* <p>
* Fast-forwards or rewinds the file to the specified position.
* <p>
* Consecutive writes on the file will begin at the new position.
* <p>
* 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;
/**
* <p>
* Closes this {@code WritableFile} which finally commits all operations
* performed on it to the underlying file system.
* <p>
* After a {@code WritableFile} has been closed all other operations will
* throw an {@link UncheckedIOException}.
* <p>
* Invoking this method on a {@link WritableFile} which has already been
* closed does nothing.
*/
@Override
void close() throws UncheckedIOException;
}

View File

@@ -1,77 +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<D extends DelegatingFolder<D, ?>> extends DelegatingNode<File>implements File {
private final D parent;
public DelegatingFile(D parent, File delegate) {
super(delegate);
this.parent = parent;
}
@Override
public Optional<D> parent() throws UncheckedIOException {
return Optional.of(parent);
}
@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);
}
}
}

View File

@@ -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<Long> quotaUsedBytes() {
return getDelegate().fileSystem().quotaUsedBytes();
}
@Override
default Optional<Long> quotaAvailableBytes() {
return getDelegate().fileSystem().quotaAvailableBytes();
}
}

View File

@@ -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<D extends DelegatingFolder<D, F>, F extends DelegatingFile<D>> extends DelegatingNode<Folder>implements Folder {
private final D parent;
private final WeakValuedCache<Folder, D> folders = WeakValuedCache.usingLoader(this::newFolder);
private final WeakValuedCache<File, F> files = WeakValuedCache.usingLoader(this::newFile);
public DelegatingFolder(D parent, Folder delegate) {
super(delegate);
this.parent = parent;
}
@Override
public Optional<D> parent() throws UncheckedIOException {
return Optional.ofNullable(parent);
}
@Override
public Stream<? extends Node> children() throws UncheckedIOException {
return AutoClosingStream.from(Stream.concat(folders(), files()));
}
@Override
public Stream<D> folders() {
return delegate.folders().map(folders::get);
}
@Override
public Stream<F> 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.");
}
}
}

View File

@@ -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<T extends Node> 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<Instant> 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 + "]";
}
}

View File

@@ -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.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 long size() throws UncheckedIOException {
return delegate.size();
}
@Override
public void position(long position) throws UncheckedIOException {
delegate.position(position);
}
@Override
public void close() throws UncheckedIOException {
delegate.close();
}
}

View File

@@ -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();
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -1,56 +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.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 (Reader reader = Channels.newReader(file.openReadable(), 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);
}
}

View File

@@ -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<ByteBuffer> byteBufferFilledWith(int value) {
if (((byte) value) != value) {
throw new IllegalArgumentException("Invalid byte value");
}
return new TypeSafeDiagnosingMatcher<ByteBuffer>(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;
}
};
}
}

View File

@@ -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<byte[]> 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<Integer> fillBufferWith(byte[] data) {
return new Answer<Integer>() {
@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);
}
}
}

View File

@@ -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"));
}
}

View File

@@ -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());
}
}

View File

@@ -1,176 +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 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));
}
}

View File

@@ -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.<Stream<? extends Folder>>when(mockFolder.folders()).thenAnswer((invocation) -> {
return Arrays.stream(new Folder[] {subFolder1});
});
List<TestDelegatingFolder> 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.<Stream<? extends File>>when(mockFolder.files()).thenAnswer((invocation) -> {
return Arrays.stream(new File[] {subFile1});
});
List<TestDelegatingFile> subFiles = delegatingFolder.files().collect(Collectors.toList());
Assert.assertThat(subFiles, Matchers.containsInAnyOrder(delegatingSubFile1));
/* files and folders */
List<Node> 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"));
}
}

View File

@@ -1,75 +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 testSize() {
ReadableFile mockReadableFile = Mockito.mock(ReadableFile.class);
@SuppressWarnings("resource")
DelegatingReadableFile delegatingReadableFile = new DelegatingReadableFile(mockReadableFile);
Mockito.when(mockReadableFile.size()).thenReturn(42l);
Assert.assertEquals(42l, delegatingReadableFile.size());
Mockito.verify(mockReadableFile).size();
}
@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();
}
}

View File

@@ -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();
}
}

View File

@@ -1,11 +0,0 @@
package org.cryptomator.filesystem.delegating;
import org.cryptomator.filesystem.File;
class TestDelegatingFile extends DelegatingFile<TestDelegatingFolder> {
public TestDelegatingFile(TestDelegatingFolder parent, File delegate) {
super(parent, delegate);
}
}

View File

@@ -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;
}
}

View File

@@ -1,22 +0,0 @@
package org.cryptomator.filesystem.delegating;
import org.cryptomator.filesystem.File;
import org.cryptomator.filesystem.Folder;
class TestDelegatingFolder extends DelegatingFolder<TestDelegatingFolder, TestDelegatingFile> {
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);
}
}

View File

@@ -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());
}
}

View File

@@ -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<Charset> CHARSETS = Arrays.asList(StandardCharsets.UTF_8, StandardCharsets.US_ASCII, StandardCharsets.UTF_16);
@DataPoints
public static final Iterable<String> 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");
}
}

View File

@@ -12,7 +12,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.1.3</version>
<version>1.2.4</version>
</parent>
<artifactId>filesystem-charsets</artifactId>
<name>Cryptomator filesystem: Charset compatibility layer</name>

View File

@@ -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<NormalizedNameFolder> {
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);
}
}

View File

@@ -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;
}
}

View File

@@ -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<NormalizedNameFolder, NormalizedNameFile> {
private static final Logger LOG = LoggerFactory.getLogger(NormalizedNameFolder.class);
private final Form displayForm;
public NormalizedNameFolder(NormalizedNameFolder parent, Folder delegate, Form displayForm) {
super(parent, delegate);
this.displayForm = displayForm;
}
@Override
public String name() throws UncheckedIOException {
return Normalizer.normalize(super.name(), displayForm);
}
@Override
public NormalizedNameFile file(String name) throws UncheckedIOException {
String nfcName = Normalizer.normalize(name, Form.NFC);
String nfdName = Normalizer.normalize(name, Form.NFD);
NormalizedNameFile nfcFile = super.file(nfcName);
NormalizedNameFile nfdFile = super.file(nfdName);
if (!nfcName.equals(nfdName) && nfcFile.exists() && nfdFile.exists()) {
LOG.warn("Ambiguous file names \"" + nfcName + "\" (NFC) vs. \"" + nfdName + "\" (NFD). Both files exist. Using \"" + nfcName + "\" (NFC).");
} else if (!nfcName.equals(nfdName) && !nfcFile.exists() && nfdFile.exists()) {
LOG.info("Moving file from \"" + nfcName + "\" (NFD) to \"" + nfdName + "\" (NFC).");
nfdFile.moveTo(nfcFile);
}
return nfcFile;
}
@Override
protected NormalizedNameFile newFile(File delegate) {
return new NormalizedNameFile(this, delegate, displayForm);
}
@Override
public NormalizedNameFolder folder(String name) throws UncheckedIOException {
String nfcName = Normalizer.normalize(name, Form.NFC);
String nfdName = Normalizer.normalize(name, Form.NFD);
NormalizedNameFolder nfcFolder = super.folder(nfcName);
NormalizedNameFolder nfdFolder = super.folder(nfdName);
if (!nfcName.equals(nfdName) && nfcFolder.exists() && nfdFolder.exists()) {
LOG.warn("Ambiguous folder names \"" + nfcName + "\" (NFC) vs. \"" + nfdName + "\" (NFD). Both files exist. Using \"" + nfcName + "\" (NFC).");
} else if (!nfcName.equals(nfdName) && !nfcFolder.exists() && nfdFolder.exists()) {
LOG.info("Moving folder from \"" + nfcName + "\" (NFD) to \"" + nfdName + "\" (NFC).");
nfdFolder.moveTo(nfcFolder);
}
return nfcFolder;
}
@Override
protected NormalizedNameFolder newFolder(Folder delegate) {
return new NormalizedNameFolder(this, delegate, displayForm);
}
}

View File

@@ -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;

View File

@@ -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());
}
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n" />
<ThresholdFilter level="WARN" onMatch="DENY" onMismatch="ACCEPT" />
</Console>
<Console name="StdErr" target="SYSTEM_ERR">
<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n" />
<ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY" />
</Console>
</Appenders>
<Loggers>
<Root level="DEBUG">
<AppenderRef ref="Console" />
<AppenderRef ref="StdErr" />
</Root>
</Loggers>
</Configuration>

View File

@@ -1 +0,0 @@
/target/

View File

@@ -12,7 +12,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.1.3</version>
<version>1.2.4</version>
</parent>
<artifactId>filesystem-crypto-integration-tests</artifactId>
<name>Cryptomator filesystem: Encryption layer tests</name>

View File

@@ -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);
}
};
}
}

View File

@@ -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:
* <code>
* DaggerCryptoFileSystemTestComponent.builder().cryptoEngineModule(new CryptoEngineTestModule()).build()
* </code>
*/
@Singleton
@Component(modules = CryptoEngineModule.class)
public interface CryptoFileSystemTestComponent {
CryptoFileSystemFactory cryptoFileSystemFactory();
ShorteningFileSystemFactory shorteningFileSystemFactory();
}

View File

@@ -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) readable.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<Integer> 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<Integer> 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<Future<Boolean>> tasks = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
final int num = nums.get(i);
final ForkJoinTask<Boolean> 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;
}
}));
}
}

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n" />
<ThresholdFilter level="WARN" onMatch="DENY" onMismatch="ACCEPT" />
</Console>
<Console name="StdErr" target="SYSTEM_ERR">
<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n" />
<ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY" />
</Console>
</Appenders>
<Loggers>
<Root level="DEBUG">
<AppenderRef ref="Console" />
<AppenderRef ref="StdErr" />
</Root>
</Loggers>
</Configuration>

View File

@@ -1 +0,0 @@
/target/

View File

@@ -12,14 +12,14 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.1.3</version>
<version>1.2.4</version>
</parent>
<artifactId>filesystem-crypto</artifactId>
<name>Cryptomator filesystem: Encryption layer</name>
<properties>
<bouncycastle.version>1.51</bouncycastle.version>
<sivmode.version>1.0.4</sivmode.version>
<sivmode.version>1.2.0</sivmode.version>
</properties>
<dependencies>

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}

View File

@@ -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 <code>false</code>. Should be <code>true</code> 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<ByteBuffer> header, long firstCleartextByte) throws IllegalArgumentException;
}

View File

@@ -1,66 +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 {
/**
* @return Number of bytes of the decrypted file.
*/
long contentLength();
/**
* 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();
}
}

View File

@@ -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();
}
}

View File

@@ -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 <a href="https://en.wikipedia.org/wiki/Deterministic_encryption">Wikipedia on deterministic encryption</a>
*/
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;
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -1,20 +0,0 @@
package org.cryptomator.crypto.engine.impl;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
public final class Constants {
private Constants() {
}
static final Collection<Integer> SUPPORTED_VAULT_VERSIONS = Collections.unmodifiableCollection(Arrays.asList(3, 4));
static final Integer CURRENT_VAULT_VERSION = 4;
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;
}

View File

@@ -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);
}
}
}

View File

@@ -1,198 +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 static org.cryptomator.crypto.engine.impl.Constants.SUPPORTED_VAULT_VERSIONS;
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> filenameCryptor = new AtomicReference<>();
private final AtomicReference<FileContentCryptor> 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 (!SUPPORTED_VAULT_VERSIONS.contains(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
}
}
}

View File

@@ -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<T> {
private final BlockingQueue<Future<T>> 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<T> processingJob) throws InterruptedException {
Future<T> 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();
}
}

View File

@@ -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<ByteBuffer> 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);
}
}

View File

@@ -1,190 +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.concurrent.atomic.LongAdder;
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<ByteBuffer> dataProcessor = new FifoParallelDataProcessor<>(NUM_THREADS + READ_AHEAD, SHARED_DECRYPTION_EXECUTOR);
private final Supplier<Mac> hmacSha256;
private final FileHeader header;
private final boolean authenticate;
private final LongAdder cleartextBytesDecrypted = new LongAdder();
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
}
@Override
public long contentLength() {
return header.getPayload().getFilesize();
}
@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<ByteBuffer> encryptionJob = new DecryptionJob(ciphertextBuffer, chunkNumber++);
dataProcessor.submit(encryptionJob);
}
}
private void submitEof() throws InterruptedException {
dataProcessor.submitPreprocessed(FileContentCryptor.EOF);
}
@Override
public ByteBuffer cleartext() throws InterruptedException {
try {
final ByteBuffer cleartext = dataProcessor.processedData();
long bytesUntilLogicalEof = contentLength() - cleartextBytesDecrypted.sum();
if (bytesUntilLogicalEof <= 0) {
return FileContentCryptor.EOF;
} else if (bytesUntilLogicalEof < cleartext.remaining()) {
cleartext.limit((int) bytesUntilLogicalEof);
}
cleartextBytesDecrypted.add(cleartext.remaining());
return cleartext;
} 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<ByteBuffer> {
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);
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More