mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-15 17:21:27 +00:00
Compare commits
191 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20e55eddf8 | ||
|
|
0fdcdc816a | ||
|
|
b7506d97a9 | ||
|
|
4ad7481dc7 | ||
|
|
bc815405d2 | ||
|
|
9c06e762c3 | ||
|
|
1ac87dd32f | ||
|
|
e0ce7ce2ec | ||
|
|
3d951a9d7b | ||
|
|
cec3d984b0 | ||
|
|
392e474cfa | ||
|
|
41fb0d51a4 | ||
|
|
aa9fef2967 | ||
|
|
adc9c02564 | ||
|
|
ace64117a2 | ||
|
|
fb4db2506b | ||
|
|
1076d971ae | ||
|
|
eed1b1cff0 | ||
|
|
f5cb82e21e | ||
|
|
67661f114b | ||
|
|
8a3e09764a | ||
|
|
eb3cfd6e6a | ||
|
|
4d1727d0e9 | ||
|
|
a51d853d1c | ||
|
|
d0039466f7 | ||
|
|
5c959989a2 | ||
|
|
6283d2df3d | ||
|
|
a9e0dfdaf8 | ||
|
|
45ca7e9e47 | ||
|
|
034b5c2718 | ||
|
|
e188649c79 | ||
|
|
1468c6ec90 | ||
|
|
07ba4eb537 | ||
|
|
414bbef1a7 | ||
|
|
e2b94ff6ef | ||
|
|
41f8a9faca | ||
|
|
1d9252e974 | ||
|
|
80780eef3c | ||
|
|
87ff33956b | ||
|
|
1804b98f05 | ||
|
|
847c6813cc | ||
|
|
1dde5ff6e7 | ||
|
|
76c9a19428 | ||
|
|
25ee0519e1 | ||
|
|
c184089c35 | ||
|
|
d2bcc47857 | ||
|
|
34629a69ea | ||
|
|
92c87f7b84 | ||
|
|
0dd96635ac | ||
|
|
048c44a6e4 | ||
|
|
06910ad1f4 | ||
|
|
02a0f3acc6 | ||
|
|
851f9240b7 | ||
|
|
99fce8d0b7 | ||
|
|
bf05c59c3b | ||
|
|
3dcebb1e1f | ||
|
|
fe3efdf610 | ||
|
|
5f4ae46f82 | ||
|
|
deef325319 | ||
|
|
fbe00a8fe3 | ||
|
|
dc87dade43 | ||
|
|
ba1625b5ad | ||
|
|
f6b126415e | ||
|
|
9147e1c08b | ||
|
|
6c18103662 | ||
|
|
6fc343ea12 | ||
|
|
d304d66cdd | ||
|
|
2ce9143b85 | ||
|
|
1c54e4f4ad | ||
|
|
9fd6f2ecae | ||
|
|
0d9f8eefc0 | ||
|
|
40a1530f19 | ||
|
|
0477a0a2e3 | ||
|
|
b77d4b5ae2 | ||
|
|
7b6c5318c5 | ||
|
|
6006d65ce0 | ||
|
|
2b01b76926 | ||
|
|
dcea9e21f0 | ||
|
|
78645ecdf6 | ||
|
|
91646dd93d | ||
|
|
fca146e939 | ||
|
|
62aa3ccc7f | ||
|
|
c0f4a2b0d3 | ||
|
|
68ee89af98 | ||
|
|
ad2c9116b9 | ||
|
|
8e24745b3e | ||
|
|
08f664e3df | ||
|
|
b6d1d1dc22 | ||
|
|
a0ef02b95c | ||
|
|
a6cefe67c4 | ||
|
|
be2b63ab2a | ||
|
|
78f11b4a5e | ||
|
|
0f20c7c3c9 | ||
|
|
d4235174f7 | ||
|
|
f16be84aa3 | ||
|
|
833f2d8566 | ||
|
|
c02a63878e | ||
|
|
6deb30307e | ||
|
|
7357829741 | ||
|
|
4bd04150c1 | ||
|
|
ac9fe28967 | ||
|
|
515755d84a | ||
|
|
cf35772c18 | ||
|
|
b0fd226c4c | ||
|
|
0d188d1c0c | ||
|
|
c6016ec7b2 | ||
|
|
e8719a1f9b | ||
|
|
27baf78029 | ||
|
|
bf5ce9a3a5 | ||
|
|
fef19fe6b3 | ||
|
|
5f56dacc4e | ||
|
|
aa249dabb5 | ||
|
|
06a5bed6e3 | ||
|
|
02f1ffc6bf | ||
|
|
bcfe040784 | ||
|
|
de9af9e303 | ||
|
|
d9b88ad1b7 | ||
|
|
e66e5b1d96 | ||
|
|
588166dce9 | ||
|
|
e2bc71a0bc | ||
|
|
e528f6827c | ||
|
|
2882ae8ef8 | ||
|
|
e37f7cea1a | ||
|
|
9b4ee10155 | ||
|
|
c9d970955c | ||
|
|
9e0afd36c4 | ||
|
|
0e523599a3 | ||
|
|
1df6589dd7 | ||
|
|
fb60c97fd3 | ||
|
|
90cd149be8 | ||
|
|
89c04ad83b | ||
|
|
f2d383a211 | ||
|
|
73fde5d020 | ||
|
|
5c0857e98e | ||
|
|
3e87b9c0c6 | ||
|
|
a1d0b6b1d3 | ||
|
|
b0d4b2e403 | ||
|
|
6996d36ea2 | ||
|
|
f77ba908da | ||
|
|
9890789c51 | ||
|
|
a385f2eaef | ||
|
|
553cb5ee3d | ||
|
|
d0dc8819f4 | ||
|
|
221deeda25 | ||
|
|
86000ac454 | ||
|
|
d026afec35 | ||
|
|
0d57ebb24a | ||
|
|
f12168ca94 | ||
|
|
d397f59565 | ||
|
|
77aaeabcde | ||
|
|
768f291ff7 | ||
|
|
6a374cc237 | ||
|
|
0133ec8fdf | ||
|
|
d9ba4935b6 | ||
|
|
b6ee29789e | ||
|
|
5ee82271f5 | ||
|
|
2eb4d87dd1 | ||
|
|
d0afeab74b | ||
|
|
cc74c2c05b | ||
|
|
8865cf0e4b | ||
|
|
65550ce70f | ||
|
|
78300f8bf1 | ||
|
|
32c65a7dda | ||
|
|
6d31ed7ea4 | ||
|
|
c3e5d3f38e | ||
|
|
e3900231aa | ||
|
|
06f13c57d4 | ||
|
|
fc1a5be85f | ||
|
|
a30b310c04 | ||
|
|
956dd855f9 | ||
|
|
67ba7cac40 | ||
|
|
9117b6bc0e | ||
|
|
bae826be28 | ||
|
|
d845e8d97a | ||
|
|
b37b2e4fb7 | ||
|
|
69f6a9927d | ||
|
|
addc9533eb | ||
|
|
8b717993ed | ||
|
|
f70d486462 | ||
|
|
293ac0ea3c | ||
|
|
e99a615b09 | ||
|
|
6da3fde864 | ||
|
|
3a725e4a16 | ||
|
|
e3256a747f | ||
|
|
adc20ea2f2 | ||
|
|
997f841662 | ||
|
|
e57b60f04e | ||
|
|
d5b4fb4fe9 | ||
|
|
edf92adfec | ||
|
|
718bacafa6 | ||
|
|
7122bdf199 |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -11,3 +11,9 @@
|
||||
.classpath
|
||||
target/
|
||||
test-output/
|
||||
|
||||
# IntelliJ Settings Files #
|
||||
.idea/
|
||||
out/
|
||||
.idea_modules/
|
||||
*.iws
|
||||
|
||||
27
.travis.yml
27
.travis.yml
@@ -1,3 +1,7 @@
|
||||
sudo: required
|
||||
|
||||
dist: trusty
|
||||
|
||||
language: java
|
||||
|
||||
jdk:
|
||||
@@ -5,13 +9,14 @@ jdk:
|
||||
|
||||
env:
|
||||
global:
|
||||
- secure: "Lgj042RD0X3rB8VZVZLWP1GetLhjd3PqI5JbJMlzgHJpDI6RkFIBLN9SWAGmkLPCehIp2zA5tu9+UVy0NNMxm9xz6SyjMCaxS28/fnYEXaNmwwDSF6O6gLUbdxyzoYIFPYOPmFxpzhebqnNIsxaM29oZpgRgUGqosCczQxiB+Ng="
|
||||
- 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"
|
||||
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 clean test jacoco:report coveralls:report
|
||||
after_success: mvn -fmain/pom.xml -Ptest-coverage clean test jacoco:report-aggregate coveralls:report
|
||||
|
||||
notifications:
|
||||
webhooks:
|
||||
@@ -26,14 +31,24 @@ notifications:
|
||||
on_success: change
|
||||
on_failure: always
|
||||
|
||||
before_deploy: mvn -fmain/pom.xml -Puber-jar clean package -DskipTests
|
||||
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.*
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
prerelease: true
|
||||
prerelease: false
|
||||
api_key:
|
||||
secure: "ZjE1j93v3qbPIe2YbmhS319aCbMdLQw0HuymmluTurxXsZtn9D4t2+eTr99vBVxGRuB5lzzGezPR5zjk5W7iHF7xhwrawXrFzr2rPJWzWFt0aM+Ry2njU1ROTGGXGTbv4anWeBlgMxLEInTAy/9ytOGNJlec83yc0THpOY2wxnk="
|
||||
file: main/uber-jar/target/Cryptomator-$TRAVIS_TAG.jar
|
||||
file:
|
||||
- "main/uber-jar/target/Cryptomator-$TRAVIS_TAG.jar"
|
||||
- "main/ant-kit/target/antkit.tar.gz"
|
||||
skip_cleanup: true
|
||||
on:
|
||||
repo: cryptomator/cryptomator
|
||||
|
||||
74
CODE_OF_CONDUCT.md
Normal file
74
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at support@cryptomator.org. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
33
CONTRIBUTING.md
Normal file
33
CONTRIBUTING.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Contributing to Cryptomator
|
||||
|
||||
## 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).
|
||||
- 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?
|
||||
|
||||
- Ask questions by [submitting a new issue](https://github.com/cryptomator/cryptomator/issues/new).
|
||||
- [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).
|
||||
|
||||
## Did you write a patch that fixes a bug?
|
||||
|
||||
- Open a new pull request with the patch.
|
||||
- Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable.
|
||||
|
||||
## Do you intend to add a new feature or change an existing one?
|
||||
|
||||
- Suggest your change by [submitting a new issue](https://github.com/cryptomator/cryptomator/issues/new) and start writing code.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Help us keep Cryptomator open and inclusive. Please read and follow our [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/master/CODE_OF_CONDUCT.md).
|
||||
|
||||
## Above all, thank you for your contributions
|
||||
|
||||
Thank you for taking the time to contribute to the project! :+1:
|
||||
|
||||
Cryptomator Team
|
||||
19
ISSUE_TEMPLATE.md
Normal file
19
ISSUE_TEMPLATE.md
Normal file
@@ -0,0 +1,19 @@
|
||||
### Basic Info
|
||||
|
||||
- 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]
|
||||
|
||||
### Description
|
||||
|
||||
[description of the bug, question or feature - what did you do? what problem occurred? etc.]
|
||||
|
||||
### Log File (optional)
|
||||
|
||||
```
|
||||
[insert relevant parts of the log file here if applicable,
|
||||
don't forget to redact sensitive information
|
||||
|
||||
on Windows: %appdata%/Cryptomator/cryptomator.log
|
||||
on OS X: ~/Library/Logs/Cryptomator/cryptomator.log
|
||||
on Debian: ~/.Cryptomator/cryptomator.log]
|
||||
```
|
||||
65
README.md
65
README.md
@@ -1,44 +1,67 @@
|
||||
Cryptomator
|
||||
====================
|
||||

|
||||
|
||||
[](https://travis-ci.org/cryptomator/cryptomator)
|
||||
[](https://scan.coverity.com/projects/cryptomator-cryptomator)
|
||||
[](https://coveralls.io/github/cryptomator/cryptomator?branch=master)
|
||||
[](https://gitter.im/cryptomator/cryptomator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](http://twitter.com/Cryptomator)
|
||||
[](https://poeditor.com/join/project/bHwbvJmx0E)
|
||||
|
||||
Multiplatform transparent client-side encryption of your files in the cloud.
|
||||
Multi-platform transparent client-side encryption of your files in the cloud.
|
||||
|
||||
If you want to take a look at the current beta version, go ahead and get your copy of cryptomator on [Cryptomator.org](https://cryptomator.org) or clone and build Cryptomator using Maven (instructions below).
|
||||
Download native binaries of Cryptomator on [cryptomator.org](https://cryptomator.org/) or clone and build Cryptomator using Maven (instructions below).
|
||||
|
||||
## Features
|
||||
- Totally transparent: Just work on the encrypted volume, as if it was an USB flash drive
|
||||
- Works with Dropbox, OneDrive (Skydrive), Google Drive and any other cloud storage, that syncs with a local directory.
|
||||
- In fact it works with any directory. You can use it to encrypt as many folders as you like
|
||||
|
||||
- Works with Dropbox, Google Drive, OneDrive, and any other cloud storage service that 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
|
||||
- Client-side. No accounts, no data shared with any online service
|
||||
- Filenames get encrypted too
|
||||
- No need to provide credentials for any 3rd party service
|
||||
- Open Source means: No backdoors. Control is better than trust
|
||||
- Use as many encrypted folders in your Dropbox as you want. Each having individual passwords
|
||||
- No commercial interest, no government agency, no wasted taxpayers' money ;-)
|
||||
- Filenames get encrypted, too
|
||||
- Use as many vaults in your Dropbox as you want, each having individual passwords
|
||||
|
||||
### Privacy
|
||||
- 256 bit keys (unlimited strength policy bundled with native binaries - 128-bit elsewhere)
|
||||
|
||||
- 256-bit keys (unlimited strength policy bundled with native binaries)
|
||||
- Scrypt key derivation
|
||||
- Cryptographically secure random numbers for salts, IVs and the master key of course
|
||||
- Sensitive data is swiped from the heap asap
|
||||
- Cryptographically secure random numbers for salts, IVs and the masterkey of course
|
||||
- Sensitive data is wiped from the heap asap
|
||||
- Lightweight: [Complexity kills security](https://www.schneier.com/essays/archives/1999/11/a_plea_for_simplicit.html)
|
||||
|
||||
### Consistency
|
||||
|
||||
- HMAC over file contents to recognize changed ciphertext before decryption
|
||||
- I/O operations are transactional and atomic, if the file systems support it
|
||||
- Each file contains all information needed for decryption (except for the key of course). No common metadata means no [SPOF](http://en.wikipedia.org/wiki/Single_point_of_failure)
|
||||
- I/O operations are transactional and atomic, if the filesystems support it
|
||||
- Each file contains all information needed for decryption (except for the key of course), no common metadata means no [SPOF](http://en.wikipedia.org/wiki/Single_point_of_failure)
|
||||
|
||||
### Security Architecture
|
||||
|
||||
For more information on the security details visit [cryptomator.org](https://cryptomator.org/architecture/).
|
||||
|
||||
## Building
|
||||
|
||||
#### Dependencies
|
||||
### Dependencies
|
||||
|
||||
* Java 8 + JCE unlimited strength policy files (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), [Debian](https://github.com/cryptomator/cryptomator-deb))
|
||||
* 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))
|
||||
|
||||
### Run Maven
|
||||
|
||||
```
|
||||
cd main
|
||||
mvn clean install
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Help us keep Cryptomator open and inclusive. Please read and follow our [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/master/CODE_OF_CONDUCT.md).
|
||||
|
||||
## License
|
||||
|
||||
Distributed under the MIT X Consortium license. See the LICENSE file for more info.
|
||||
Distributed under the MIT X Consortium license. See the `LICENSES/MIT-X-Consortium-License.txt` file for more info.
|
||||
|
||||
BIN
cryptomator.png
Normal file
BIN
cryptomator.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
1
main/ant-kit/.gitignore
vendored
Normal file
1
main/ant-kit/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target/
|
||||
37
main/ant-kit/assembly.xml
Normal file
37
main/ant-kit/assembly.xml
Normal file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
|
||||
<id>tarball</id>
|
||||
<includeBaseDirectory>false</includeBaseDirectory>
|
||||
<formats>
|
||||
<format>tar.gz</format>
|
||||
</formats>
|
||||
<fileSets>
|
||||
<fileSet>
|
||||
<directory>target/libs</directory>
|
||||
<includes>
|
||||
<include>*.jar</include>
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/fixed-binaries</directory>
|
||||
<filtered>false</filtered>
|
||||
<outputDirectory>fixed-binaries</outputDirectory>
|
||||
<fileMode>755</fileMode>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/package</directory>
|
||||
<filtered>false</filtered>
|
||||
<outputDirectory>package</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target</directory>
|
||||
<includes>
|
||||
<include>build.xml</include>
|
||||
</includes>
|
||||
<filtered>false</filtered>
|
||||
<outputDirectory>.</outputDirectory>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
</assembly>
|
||||
103
main/ant-kit/pom.xml
Normal file
103
main/ant-kit/pom.xml
Normal file
@@ -0,0 +1,103 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright (c) 2016 Sebastian Stenzel
|
||||
This file is licensed under the terms of the MIT license.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.1.2</version>
|
||||
</parent>
|
||||
<artifactId>ant-kit</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<name>Cryptomator Ant Build Kit</name>
|
||||
<description>Builds a package that can be built with Ant locally</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>ui</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- copy libraries to target/libs/: -->
|
||||
<plugin>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-libs</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/libs</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- copy resources to target/: -->
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>2.7</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-resources</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}</outputDirectory>
|
||||
<escapeString>\</escapeString>
|
||||
<encoding>UTF-8</encoding>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
<excludes>
|
||||
<exclude>fixed-binaries/**</exclude>
|
||||
</excludes>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>false</filtering>
|
||||
<includes>
|
||||
<include>fixed-binaries/**</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- create antkit.tar.gz: -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-assembly</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>assembly.xml</descriptor>
|
||||
</descriptors>
|
||||
<appendAssemblyId>false</appendAssemblyId>
|
||||
<finalName>antkit</finalName>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
80
main/ant-kit/src/main/resources/build.xml
Normal file
80
main/ant-kit/src/main/resources/build.xml
Normal file
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project name="Cryptomator" default="create-jar" basedir="." xmlns:fx="javafx:com.sun.javafx.tools.ant">
|
||||
<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" />
|
||||
|
||||
<!-- 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:resources>
|
||||
<fx:fileset dir="libs" type="jar" includes="*.jar" excludes="ui-${project.version}.jar" />
|
||||
</fx:resources>
|
||||
<fx:manifest>
|
||||
<fx:attribute name="Implementation-Vendor" value="cryptomator.org" />
|
||||
<fx:attribute name="Implementation-Title" value="Cryptomator"/>
|
||||
<fx:attribute name="Implementation-Version" value="${project.version}" />
|
||||
</fx:manifest>
|
||||
</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">
|
||||
<fx:application refid="Cryptomator" />
|
||||
<fx:info title="Cryptomator" vendor="cryptomator.org" copyright="cryptomator.org" license="MIT" category="Utility">
|
||||
<fx:association mimetype="application/x-vnd.cryptomator-vault-metadata" extension="cryptomator" description="Cryptomator Vault Metadata" />
|
||||
</fx:info>
|
||||
<fx:platform j2se="8.0">
|
||||
<fx:property name="cryptomator.logPath" value="~/.Cryptomator/cryptomator.log" />
|
||||
<fx:jvmarg value="-Xmx1048m"/>
|
||||
</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="fixed-binaries" type="data" includes="linux-launcher-*" arch=""/>
|
||||
</fx:resources>
|
||||
<fx:permissions elevated="false" />
|
||||
<fx:preferences install="true" />
|
||||
</fx:deploy>
|
||||
</target>
|
||||
|
||||
<!-- Create Red Hat package -->
|
||||
<target name="rpm" depends="create-jar">
|
||||
<fx:deploy nativeBundles="rpm" outdir="antbuild" outfile="Cryptomator-${project.version}" verbose="true">
|
||||
<fx:application refid="Cryptomator" />
|
||||
<fx:info title="Cryptomator" vendor="cryptomator.org" copyright="cryptomator.org" license="MIT" category="Utility">
|
||||
<fx:association mimetype="application/x-vnd.cryptomator-vault-metadata" extension="cryptomator" description="Cryptomator Vault Metadata" />
|
||||
</fx:info>
|
||||
<fx:platform j2se="8.0">
|
||||
<fx:property name="cryptomator.logPath" value="~/.Cryptomator/cryptomator.log" />
|
||||
<fx:jvmarg value="-Xmx1048m"/>
|
||||
</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:permissions elevated="false" />
|
||||
<fx:preferences install="true" />
|
||||
</fx:deploy>
|
||||
</target>
|
||||
|
||||
</project>
|
||||
Binary file not shown.
Binary file not shown.
BIN
main/ant-kit/src/main/resources/package/linux/Cryptomator.png
Normal file
BIN
main/ant-kit/src/main/resources/package/linux/Cryptomator.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
16
main/ant-kit/src/main/resources/package/linux/control
Normal file
16
main/ant-kit/src/main/resources/package/linux/control
Normal file
@@ -0,0 +1,16 @@
|
||||
Package: APPLICATION_PACKAGE
|
||||
Version: APPLICATION_VERSION
|
||||
Section: contrib/utils
|
||||
Maintainer: Sebastian Stenzel <sebastian.stenzel@gmail.com>
|
||||
Homepage: https://cryptomator.org
|
||||
Vcs-Git: https://github.com/totalvoidness/cryptomator.git
|
||||
Vcs-Browser: https://github.com/totalvoidness/cryptomator
|
||||
Priority: optional
|
||||
Architecture: APPLICATION_ARCH
|
||||
Provides: APPLICATION_PACKAGE
|
||||
Installed-Size: APPLICATION_INSTALLED_SIZE
|
||||
Depends: gvfs-bin, gvfs-backends, gvfs-fuse, xdg-utils
|
||||
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
|
||||
you save on one of these volumes will end up encrypted inside your vault.
|
||||
23
main/ant-kit/src/main/resources/package/linux/copyright
Normal file
23
main/ant-kit/src/main/resources/package/linux/copyright
Normal file
@@ -0,0 +1,23 @@
|
||||
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: cryptomator
|
||||
Source: <https://github.com/totalvoidness/cryptomator>
|
||||
|
||||
Copyright: 2015 Sebastian Stenzel <sebastian.stenzel@gmail.com> and contributors.
|
||||
License: MIT
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
.
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
50
main/ant-kit/src/main/resources/package/linux/postinst
Normal file
50
main/ant-kit/src/main/resources/package/linux/postinst
Normal file
@@ -0,0 +1,50 @@
|
||||
#!/bin/sh
|
||||
# postinst script for APPLICATION_NAME
|
||||
#
|
||||
# see: dh_installdeb(1)
|
||||
|
||||
set -e
|
||||
|
||||
# summary of how this script can be called:
|
||||
# * <postinst> `configure' <most-recently-configured-version>
|
||||
# * <old-postinst> `abort-upgrade' <new version>
|
||||
# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
|
||||
# <new-version>
|
||||
# * <postinst> `abort-remove'
|
||||
# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
|
||||
# <failed-install-package> <version> `removing'
|
||||
# <conflicting-package> <version>
|
||||
# for details, see http://www.debian.org/doc/debian-policy/ or
|
||||
# the debian-policy package
|
||||
|
||||
case "$1" in
|
||||
configure)
|
||||
echo Adding shortcut to the menu
|
||||
SECONDARY_LAUNCHERS_INSTALL
|
||||
APP_CDS_CACHE
|
||||
xdg-desktop-menu install --novendor /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME.desktop
|
||||
FILE_ASSOCIATION_INSTALL
|
||||
|
||||
rm /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME
|
||||
if [ $(uname -m) = "x86_64" ]; then
|
||||
mv /opt/APPLICATION_FS_NAME/app/linux-launcher-x64 /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME
|
||||
else
|
||||
mv /opt/APPLICATION_FS_NAME/app/linux-launcher-x86 /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME
|
||||
fi
|
||||
;;
|
||||
|
||||
abort-upgrade|abort-remove|abort-deconfigure)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "postinst called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# dh_installdeb will replace this with shell code automatically
|
||||
# generated by other debhelper scripts.
|
||||
|
||||
#DEBHELPER#
|
||||
|
||||
exit 0
|
||||
54
main/ant-kit/src/main/resources/package/linux/spec
Normal file
54
main/ant-kit/src/main/resources/package/linux/spec
Normal file
@@ -0,0 +1,54 @@
|
||||
Summary: APPLICATION_SUMMARY
|
||||
Name: APPLICATION_PACKAGE
|
||||
Version: APPLICATION_VERSION
|
||||
Release: 1
|
||||
License: APPLICATION_LICENSE_TYPE
|
||||
Vendor: APPLICATION_VENDOR
|
||||
Prefix: /opt
|
||||
Provides: APPLICATION_PACKAGE
|
||||
Requires: ld-linux.so.2 libX11.so.6 libXext.so.6 libXi.so.6 libXrender.so.1 libXtst.so.6 libasound.so.2 libc.so.6 libdl.so.2 libgcc_s.so.1 libm.so.6 libpthread.so.0 libthread_db.so.1
|
||||
Autoprov: 0
|
||||
Autoreq: 0
|
||||
|
||||
#avoid ARCH subfolder
|
||||
%define _rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm
|
||||
|
||||
#comment line below to enable effective jar compression
|
||||
#it could easily get your package size from 40 to 15Mb but
|
||||
#build time will substantially increase and it may require unpack200/system java to install
|
||||
%define __jar_repack %{nil}
|
||||
|
||||
%description
|
||||
APPLICATION_DESCRIPTION
|
||||
|
||||
%prep
|
||||
|
||||
%build
|
||||
|
||||
%install
|
||||
rm -rf %{buildroot}
|
||||
mkdir -p %{buildroot}/opt
|
||||
cp -r %{_sourcedir}/APPLICATION_FS_NAME %{buildroot}/opt
|
||||
|
||||
%files
|
||||
APPLICATION_LICENSE_FILE
|
||||
/opt/APPLICATION_FS_NAME
|
||||
|
||||
%post
|
||||
SECONDARY_LAUNCHERS_INSTALL
|
||||
APP_CDS_CACHE
|
||||
xdg-desktop-menu install --novendor /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME.desktop
|
||||
FILE_ASSOCIATION_INSTALL
|
||||
rm /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME
|
||||
if [ $(uname -m) = "x86_64" ]; then
|
||||
mv /opt/APPLICATION_FS_NAME/app/linux-launcher-x64 /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME
|
||||
else
|
||||
mv /opt/APPLICATION_FS_NAME/app/linux-launcher-x86 /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME
|
||||
fi
|
||||
|
||||
%preun
|
||||
SECONDARY_LAUNCHERS_REMOVE
|
||||
xdg-desktop-menu uninstall --novendor /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME.desktop
|
||||
FILE_ASSOCIATION_REMOVE
|
||||
|
||||
%clean
|
||||
@@ -1,23 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright (c) 2015 Markus Kreusch This file is licensed under the terms
|
||||
of the MIT license. See the LICENSE.txt file for more info. -->
|
||||
<!--
|
||||
Copyright (c) 2015 Markus Kreusch
|
||||
This file is licensed under the terms of the MIT license.
|
||||
See the LICENSE.txt file for more info.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.1.2</version>
|
||||
</parent>
|
||||
<artifactId>commons-test</artifactId>
|
||||
<name>Cryptomator common test dependencies</name>
|
||||
<description>Shared utilities for tests</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>commons</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>de.bechte.junit</groupId>
|
||||
<artifactId>junit-hierarchicalcontextrunner</artifactId>
|
||||
@@ -26,11 +38,6 @@
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>commons</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1,29 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright (c) 2015 Markus Kreusch This file is licensed under the terms
|
||||
of the MIT license. See the LICENSE.txt file for more info. -->
|
||||
<!--
|
||||
Copyright (c) 2015 Markus Kreusch
|
||||
This file is licensed under the terms of the MIT license.
|
||||
See the LICENSE.txt file for more info.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.1.2</version>
|
||||
</parent>
|
||||
<artifactId>commons</artifactId>
|
||||
<name>Cryptomator common</name>
|
||||
<description>Shared utilities</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- Libs -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- DI -->
|
||||
<dependency>
|
||||
<groupId>com.google.dagger</groupId>
|
||||
<artifactId>dagger</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.dagger</groupId>
|
||||
<artifactId>dagger-compiler</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Test -->
|
||||
<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>
|
||||
@@ -35,4 +60,13 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.cryptomator.common;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@Module
|
||||
public class CommonsModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("SemVer")
|
||||
Comparator<String> providesSemVerComparator() {
|
||||
return new SemVerComparator();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.util;
|
||||
package org.cryptomator.common;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
@@ -6,10 +6,11 @@
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.util;
|
||||
package org.cryptomator.common;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import org.cryptomator.common.SemVerComparator;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.1.2</version>
|
||||
</parent>
|
||||
<artifactId>filesystem-api</artifactId>
|
||||
<name>Cryptomator filesystem: API</name>
|
||||
|
||||
@@ -12,7 +12,7 @@ import static java.lang.String.format;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class FileSystemVisitor {
|
||||
public class FolderVisitor {
|
||||
|
||||
private final Consumer<Folder> beforeFolderVisitor;
|
||||
private final Consumer<Folder> afterFolderVisitor;
|
||||
@@ -20,7 +20,7 @@ public class FileSystemVisitor {
|
||||
private final Consumer<Node> nodeVisitor;
|
||||
private final int maxDepth;
|
||||
|
||||
public FileSystemVisitor(FileSystemVisitorBuilder builder) {
|
||||
public FolderVisitor(FolderVisitorBuilder builder) {
|
||||
this.beforeFolderVisitor = builder.beforeFolderVisitor;
|
||||
this.afterFolderVisitor = builder.afterFolderVisitor;
|
||||
this.fileVisitor = builder.fileVisitor;
|
||||
@@ -28,19 +28,19 @@ public class FileSystemVisitor {
|
||||
this.maxDepth = builder.maxDepth;
|
||||
}
|
||||
|
||||
public static FileSystemVisitorBuilder fileSystemVisitor() {
|
||||
return new FileSystemVisitorBuilder();
|
||||
public static FolderVisitorBuilder folderVisitor() {
|
||||
return new FolderVisitorBuilder();
|
||||
}
|
||||
|
||||
public FileSystemVisitor visit(Folder folder) {
|
||||
public FolderVisitor visit(Folder folder) {
|
||||
return visit(folder, 0);
|
||||
}
|
||||
|
||||
public FileSystemVisitor visit(File file) {
|
||||
public FolderVisitor visit(File file) {
|
||||
return visit(file, 0);
|
||||
}
|
||||
|
||||
private FileSystemVisitor visit(Folder folder, int depth) {
|
||||
private FolderVisitor visit(Folder folder, int depth) {
|
||||
beforeFolderVisitor.accept(folder);
|
||||
nodeVisitor.accept(folder);
|
||||
final int childDepth = depth + 1;
|
||||
@@ -52,13 +52,13 @@ public class FileSystemVisitor {
|
||||
return this;
|
||||
}
|
||||
|
||||
private FileSystemVisitor visit(File file, int depth) {
|
||||
private FolderVisitor visit(File file, int depth) {
|
||||
nodeVisitor.accept(file);
|
||||
fileVisitor.accept(file);
|
||||
return this;
|
||||
}
|
||||
|
||||
public static class FileSystemVisitorBuilder {
|
||||
public static class FolderVisitorBuilder {
|
||||
|
||||
private Consumer<Folder> beforeFolderVisitor = noOp();
|
||||
private Consumer<Folder> afterFolderVisitor = noOp();
|
||||
@@ -66,10 +66,10 @@ public class FileSystemVisitor {
|
||||
private Consumer<Node> nodeVisitor = noOp();
|
||||
private int maxDepth = Integer.MAX_VALUE;
|
||||
|
||||
private FileSystemVisitorBuilder() {
|
||||
private FolderVisitorBuilder() {
|
||||
}
|
||||
|
||||
public FileSystemVisitorBuilder beforeFolder(Consumer<Folder> beforeFolderVisitor) {
|
||||
public FolderVisitorBuilder beforeFolder(Consumer<Folder> beforeFolderVisitor) {
|
||||
if (beforeFolderVisitor == null) {
|
||||
throw new IllegalArgumentException("Vistior may not be null");
|
||||
}
|
||||
@@ -77,7 +77,7 @@ public class FileSystemVisitor {
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileSystemVisitorBuilder afterFolder(Consumer<Folder> afterFolderVisitor) {
|
||||
public FolderVisitorBuilder afterFolder(Consumer<Folder> afterFolderVisitor) {
|
||||
if (afterFolderVisitor == null) {
|
||||
throw new IllegalArgumentException("Vistior may not be null");
|
||||
}
|
||||
@@ -85,7 +85,7 @@ public class FileSystemVisitor {
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileSystemVisitorBuilder forEachFile(Consumer<File> fileVisitor) {
|
||||
public FolderVisitorBuilder forEachFile(Consumer<File> fileVisitor) {
|
||||
if (fileVisitor == null) {
|
||||
throw new IllegalArgumentException("Vistior may not be null");
|
||||
}
|
||||
@@ -93,7 +93,7 @@ public class FileSystemVisitor {
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileSystemVisitorBuilder forEachNode(Consumer<Node> nodeVisitor) {
|
||||
public FolderVisitorBuilder forEachNode(Consumer<Node> nodeVisitor) {
|
||||
if (nodeVisitor == null) {
|
||||
throw new IllegalArgumentException("Vistior may not be null");
|
||||
}
|
||||
@@ -101,7 +101,7 @@ public class FileSystemVisitor {
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileSystemVisitorBuilder withMaxDepth(int maxDepth) {
|
||||
public FolderVisitorBuilder withMaxDepth(int maxDepth) {
|
||||
if (maxDepth < 0) {
|
||||
throw new IllegalArgumentException(format("maxDepth must not be smaller 0 but was %d", maxDepth));
|
||||
}
|
||||
@@ -109,16 +109,16 @@ public class FileSystemVisitor {
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileSystemVisitor visit(Folder folder) {
|
||||
public FolderVisitor visit(Folder folder) {
|
||||
return build().visit(folder);
|
||||
}
|
||||
|
||||
public FileSystemVisitor visit(File file) {
|
||||
public FolderVisitor visit(File file) {
|
||||
return build().visit(file);
|
||||
}
|
||||
|
||||
public FileSystemVisitor build() {
|
||||
return new FileSystemVisitor(this);
|
||||
public FolderVisitor build() {
|
||||
return new FolderVisitor(this);
|
||||
}
|
||||
|
||||
private static <T> Consumer<T> noOp() {
|
||||
@@ -13,6 +13,7 @@ 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;
|
||||
@@ -35,7 +36,7 @@ public abstract class DelegatingFolder<D extends DelegatingFolder<D, F>, F exten
|
||||
|
||||
@Override
|
||||
public Stream<? extends Node> children() throws UncheckedIOException {
|
||||
return Stream.concat(folders(), files());
|
||||
return AutoClosingStream.from(Stream.concat(folders(), files()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
45
main/filesystem-charsets/pom.xml
Normal file
45
main/filesystem-charsets/pom.xml
Normal file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright (c) 2015 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
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.1.2</version>
|
||||
</parent>
|
||||
<artifactId>filesystem-charsets</artifactId>
|
||||
<name>Cryptomator filesystem: Charset compatibility layer</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>filesystem-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Tests -->
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>commons-test</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>filesystem-inmemory</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,32 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.filesystem.charsets;
|
||||
|
||||
import java.io.UncheckedIOException;
|
||||
import java.text.Normalizer;
|
||||
import java.text.Normalizer.Form;
|
||||
|
||||
import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.delegating.DelegatingFile;
|
||||
|
||||
class NormalizedNameFile extends DelegatingFile<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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.filesystem.charsets;
|
||||
|
||||
import java.text.Normalizer.Form;
|
||||
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
import org.cryptomator.filesystem.delegating.DelegatingFileSystem;
|
||||
|
||||
public class NormalizedNameFileSystem extends NormalizedNameFolder implements DelegatingFileSystem {
|
||||
|
||||
public NormalizedNameFileSystem(Folder delegate, Form displayForm) {
|
||||
super(null, delegate, displayForm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Folder getDelegate() {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.filesystem.charsets;
|
||||
|
||||
import java.io.UncheckedIOException;
|
||||
import java.text.Normalizer;
|
||||
import java.text.Normalizer.Form;
|
||||
|
||||
import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
import org.cryptomator.filesystem.delegating.DelegatingFolder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
class NormalizedNameFolder extends DelegatingFolder<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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
/**
|
||||
* Makes sure, the filesystems wrapped by this filesystem work only on UTF-8 encoded file paths using Normalization Form C.
|
||||
* Filesystems wrapping this file system, on the other hand, will get filenames reported in a specified Normalization Form.
|
||||
* It is recommended to use NFD for OS X and NFC for other operating systems.
|
||||
* When looking for a file or folder with a name given in either form, both possibilities are considered
|
||||
* and files/folders stored in NFD are automatically migrated to NFC.
|
||||
*/
|
||||
package org.cryptomator.filesystem.charsets;
|
||||
@@ -0,0 +1,90 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.filesystem.charsets;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.text.Normalizer.Form;
|
||||
|
||||
import org.cryptomator.filesystem.FileSystem;
|
||||
import org.cryptomator.filesystem.WritableFile;
|
||||
import org.cryptomator.filesystem.inmem.InMemoryFileSystem;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class NormalizedNameFileSystemTest {
|
||||
|
||||
@Test
|
||||
public void testFileMigration() {
|
||||
FileSystem inMemoryFs = new InMemoryFileSystem();
|
||||
try (WritableFile writable = inMemoryFs.file("\u006F\u0302").openWritable()) {
|
||||
writable.write(ByteBuffer.allocate(0));
|
||||
}
|
||||
FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFC);
|
||||
Assert.assertTrue(normalizationFs.file("\u00F4").exists());
|
||||
Assert.assertTrue(normalizationFs.file("\u006F\u0302").exists());
|
||||
Assert.assertFalse(inMemoryFs.file("\u006F\u0302").exists());
|
||||
Assert.assertTrue(inMemoryFs.file("\u00F4").exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoFileMigration() {
|
||||
FileSystem inMemoryFs = new InMemoryFileSystem();
|
||||
try (WritableFile writable = inMemoryFs.file("\u00F4").openWritable()) {
|
||||
writable.write(ByteBuffer.allocate(0));
|
||||
}
|
||||
FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFC);
|
||||
Assert.assertTrue(normalizationFs.file("\u00F4").exists());
|
||||
Assert.assertTrue(normalizationFs.file("\u006F\u0302").exists());
|
||||
Assert.assertFalse(inMemoryFs.file("\u006F\u0302").exists());
|
||||
Assert.assertTrue(inMemoryFs.file("\u00F4").exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFolderMigration() {
|
||||
FileSystem inMemoryFs = new InMemoryFileSystem();
|
||||
inMemoryFs.folder("\u006F\u0302").create();
|
||||
FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFC);
|
||||
Assert.assertTrue(normalizationFs.folder("\u00F4").exists());
|
||||
Assert.assertTrue(normalizationFs.folder("\u006F\u0302").exists());
|
||||
Assert.assertFalse(inMemoryFs.folder("\u006F\u0302").exists());
|
||||
Assert.assertTrue(inMemoryFs.folder("\u00F4").exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoFolderMigration() {
|
||||
FileSystem inMemoryFs = new InMemoryFileSystem();
|
||||
inMemoryFs.folder("\u00F4").create();
|
||||
FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFC);
|
||||
Assert.assertTrue(normalizationFs.folder("\u00F4").exists());
|
||||
Assert.assertTrue(normalizationFs.folder("\u006F\u0302").exists());
|
||||
Assert.assertFalse(inMemoryFs.folder("\u006F\u0302").exists());
|
||||
Assert.assertTrue(inMemoryFs.folder("\u00F4").exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNfcDisplayNames() {
|
||||
FileSystem inMemoryFs = new InMemoryFileSystem();
|
||||
inMemoryFs.folder("a\u00F4").create();
|
||||
inMemoryFs.folder("b\u006F\u0302").create();
|
||||
FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFC);
|
||||
Assert.assertEquals("a\u00F4", normalizationFs.folder("a\u00F4").name());
|
||||
Assert.assertEquals("b\u00F4", normalizationFs.folder("b\u006F\u0302").name());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNfdDisplayNames() {
|
||||
FileSystem inMemoryFs = new InMemoryFileSystem();
|
||||
inMemoryFs.folder("a\u00F4").create();
|
||||
inMemoryFs.folder("b\u006F\u0302").create();
|
||||
FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFD);
|
||||
Assert.assertEquals("a\u006F\u0302", normalizationFs.folder("a\u00F4").name());
|
||||
Assert.assertEquals("b\u006F\u0302", normalizationFs.folder("b\u006F\u0302").name());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.filesystem.charsets;
|
||||
|
||||
import java.text.Normalizer.Form;
|
||||
|
||||
import org.cryptomator.filesystem.File;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
public class NormalizedNameFileTest {
|
||||
|
||||
private File delegateNfc;
|
||||
private File delegateNfd;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
delegateNfc = Mockito.mock(File.class);
|
||||
delegateNfd = Mockito.mock(File.class);
|
||||
Mockito.when(delegateNfc.name()).thenReturn("\u00C5");
|
||||
Mockito.when(delegateNfd.name()).thenReturn("\u0041\u030A");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisplayNameInNfc() {
|
||||
File file1 = new NormalizedNameFile(null, delegateNfc, Form.NFC);
|
||||
File file2 = new NormalizedNameFile(null, delegateNfd, Form.NFC);
|
||||
Assert.assertEquals("\u00C5", file1.name());
|
||||
Assert.assertEquals("\u00C5", file2.name());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisplayNameInNfd() {
|
||||
File file1 = new NormalizedNameFile(null, delegateNfc, Form.NFD);
|
||||
File file2 = new NormalizedNameFile(null, delegateNfd, Form.NFD);
|
||||
Assert.assertEquals("\u0041\u030A", file1.name());
|
||||
Assert.assertEquals("\u0041\u030A", file2.name());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.filesystem.charsets;
|
||||
|
||||
import java.text.Normalizer.Form;
|
||||
|
||||
import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
public class NormalizedNameFolderTest {
|
||||
|
||||
private Folder delegate;
|
||||
private File delegateSubFileNfc;
|
||||
private File delegateSubFileNfd;
|
||||
private Folder delegateSubFolderNfc;
|
||||
private Folder delegateSubFolderNfd;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
delegate = Mockito.mock(Folder.class);
|
||||
delegateSubFileNfc = Mockito.mock(File.class);
|
||||
delegateSubFileNfd = Mockito.mock(File.class);
|
||||
Mockito.when(delegate.file("\u00C5")).thenReturn(delegateSubFileNfc);
|
||||
Mockito.when(delegateSubFileNfc.name()).thenReturn("\u00C5");
|
||||
Mockito.when(delegate.file("\u0041\u030A")).thenReturn(delegateSubFileNfd);
|
||||
Mockito.when(delegateSubFileNfd.name()).thenReturn("\u0041\u030A");
|
||||
delegateSubFolderNfc = Mockito.mock(Folder.class);
|
||||
delegateSubFolderNfd = Mockito.mock(Folder.class);
|
||||
Mockito.when(delegate.folder("\u00F4")).thenReturn(delegateSubFolderNfc);
|
||||
Mockito.when(delegateSubFolderNfc.name()).thenReturn("\u00F4");
|
||||
Mockito.when(delegate.folder("\u006F\u0302")).thenReturn(delegateSubFolderNfd);
|
||||
Mockito.when(delegateSubFolderNfd.name()).thenReturn("\u006F\u0302");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisplayNameInNfc() {
|
||||
Folder folder1 = new NormalizedNameFolder(null, delegateSubFolderNfc, Form.NFC);
|
||||
Folder folder2 = new NormalizedNameFolder(null, delegateSubFolderNfd, Form.NFC);
|
||||
Assert.assertEquals("\u00F4", folder1.name());
|
||||
Assert.assertEquals("\u00F4", folder2.name());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisplayNameInNfd() {
|
||||
Folder folder1 = new NormalizedNameFolder(null, delegateSubFolderNfc, Form.NFD);
|
||||
Folder folder2 = new NormalizedNameFolder(null, delegateSubFolderNfd, Form.NFD);
|
||||
Assert.assertEquals("\u006F\u0302", folder1.name());
|
||||
Assert.assertEquals("\u006F\u0302", folder2.name());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoFolderMigration1() {
|
||||
Mockito.when(delegateSubFolderNfc.exists()).thenReturn(true);
|
||||
Mockito.when(delegateSubFolderNfd.exists()).thenReturn(false);
|
||||
Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC);
|
||||
Folder sub1 = folder.folder("\u00F4");
|
||||
Folder sub2 = folder.folder("\u006F\u0302");
|
||||
Mockito.verify(delegateSubFolderNfd, Mockito.never()).moveTo(Mockito.any());
|
||||
Assert.assertSame(sub1, sub2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoFolderMigration2() {
|
||||
Mockito.when(delegateSubFolderNfc.exists()).thenReturn(true);
|
||||
Mockito.when(delegateSubFolderNfd.exists()).thenReturn(true);
|
||||
Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC);
|
||||
Folder sub1 = folder.folder("\u00F4");
|
||||
Folder sub2 = folder.folder("\u006F\u0302");
|
||||
Mockito.verify(delegateSubFolderNfd, Mockito.never()).moveTo(Mockito.any());
|
||||
Assert.assertSame(sub1, sub2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoFolderMigration3() {
|
||||
Mockito.when(delegateSubFolderNfc.exists()).thenReturn(false);
|
||||
Mockito.when(delegateSubFolderNfd.exists()).thenReturn(false);
|
||||
Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC);
|
||||
Folder sub1 = folder.folder("\u00F4");
|
||||
Folder sub2 = folder.folder("\u006F\u0302");
|
||||
Mockito.verify(delegateSubFolderNfd, Mockito.never()).moveTo(Mockito.any());
|
||||
Assert.assertSame(sub1, sub2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFolderMigration() {
|
||||
Mockito.when(delegateSubFolderNfc.exists()).thenReturn(false);
|
||||
Mockito.when(delegateSubFolderNfd.exists()).thenReturn(true);
|
||||
Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC);
|
||||
Folder sub1 = folder.folder("\u00F4");
|
||||
Mockito.verify(delegateSubFolderNfd).moveTo(delegateSubFolderNfc);
|
||||
Folder sub2 = folder.folder("\u006F\u0302");
|
||||
Assert.assertSame(sub1, sub2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoFileMigration1() {
|
||||
Mockito.when(delegateSubFileNfc.exists()).thenReturn(true);
|
||||
Mockito.when(delegateSubFileNfd.exists()).thenReturn(false);
|
||||
Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC);
|
||||
File sub1 = folder.file("\u00C5");
|
||||
File sub2 = folder.file("\u0041\u030A");
|
||||
Mockito.verify(delegateSubFileNfd, Mockito.never()).moveTo(Mockito.any());
|
||||
Assert.assertSame(sub1, sub2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoFileMigration2() {
|
||||
Mockito.when(delegateSubFileNfc.exists()).thenReturn(true);
|
||||
Mockito.when(delegateSubFileNfd.exists()).thenReturn(true);
|
||||
Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC);
|
||||
File sub1 = folder.file("\u00C5");
|
||||
File sub2 = folder.file("\u0041\u030A");
|
||||
Mockito.verify(delegateSubFileNfd, Mockito.never()).moveTo(Mockito.any());
|
||||
Assert.assertSame(sub1, sub2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoFileMigration3() {
|
||||
Mockito.when(delegateSubFileNfc.exists()).thenReturn(false);
|
||||
Mockito.when(delegateSubFileNfd.exists()).thenReturn(false);
|
||||
Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC);
|
||||
File sub1 = folder.file("\u00C5");
|
||||
File sub2 = folder.file("\u0041\u030A");
|
||||
Mockito.verify(delegateSubFileNfd, Mockito.never()).moveTo(Mockito.any());
|
||||
Assert.assertSame(sub1, sub2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileMigration() {
|
||||
Mockito.when(delegateSubFileNfc.exists()).thenReturn(false);
|
||||
Mockito.when(delegateSubFileNfd.exists()).thenReturn(true);
|
||||
Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC);
|
||||
File sub1 = folder.file("\u00C5");
|
||||
Mockito.verify(delegateSubFileNfd).moveTo(delegateSubFileNfc);
|
||||
File sub2 = folder.file("\u0041\u030A");
|
||||
Assert.assertSame(sub1, sub2);
|
||||
}
|
||||
|
||||
}
|
||||
22
main/filesystem-charsets/src/test/resources/log4j2.xml
Normal file
22
main/filesystem-charsets/src/test/resources/log4j2.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?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>
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.1.2</version>
|
||||
</parent>
|
||||
<artifactId>filesystem-crypto-integration-tests</artifactId>
|
||||
<name>Cryptomator filesystem: Encryption layer tests</name>
|
||||
|
||||
@@ -12,14 +12,14 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.1.2</version>
|
||||
</parent>
|
||||
<artifactId>filesystem-crypto</artifactId>
|
||||
<name>Cryptomator filesystem: Encryption layer</name>
|
||||
|
||||
<properties>
|
||||
<bouncycastle.version>1.51</bouncycastle.version>
|
||||
<sivmode.version>1.0.2</sivmode.version>
|
||||
<sivmode.version>1.0.4</sivmode.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.crypto.engine;
|
||||
|
||||
abstract class CryptoException extends RuntimeException {
|
||||
public abstract class CryptoException extends RuntimeException {
|
||||
|
||||
public CryptoException() {
|
||||
super();
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
*******************************************************************************/
|
||||
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.
|
||||
@@ -22,12 +24,9 @@ public interface FilenameCryptor {
|
||||
String hashDirectoryId(String cleartextDirectoryId);
|
||||
|
||||
/**
|
||||
* Tests without an actual decryption attempt, if a name is a well-formed ciphertext.
|
||||
*
|
||||
* @param ciphertextName Filename in question
|
||||
* @return <code>true</code> if the given name is likely to be a valid ciphertext
|
||||
* @return A Pattern that can be used to test, if a name is a well-formed ciphertext.
|
||||
*/
|
||||
boolean isEncryptedFilename(String ciphertextName);
|
||||
Pattern encryptedNamePattern();
|
||||
|
||||
/**
|
||||
* @param cleartextName original filename including cleartext file extension
|
||||
|
||||
@@ -11,28 +11,28 @@ package org.cryptomator.crypto.engine;
|
||||
public class UnsupportedVaultFormatException extends CryptoException {
|
||||
|
||||
private final Integer detectedVersion;
|
||||
private final Integer supportedVersion;
|
||||
private final Integer latestSupportedVersion;
|
||||
|
||||
public UnsupportedVaultFormatException(Integer detectedVersion, Integer supportedVersion) {
|
||||
super("Tried to open vault of version " + detectedVersion + ", but can only handle version " + supportedVersion);
|
||||
public UnsupportedVaultFormatException(Integer detectedVersion, Integer latestSupportedVersion) {
|
||||
super("Tried to open vault of version " + detectedVersion + ", latest supported version is " + latestSupportedVersion);
|
||||
this.detectedVersion = detectedVersion;
|
||||
this.supportedVersion = supportedVersion;
|
||||
this.latestSupportedVersion = latestSupportedVersion;
|
||||
}
|
||||
|
||||
public Integer getDetectedVersion() {
|
||||
return detectedVersion;
|
||||
}
|
||||
|
||||
public Integer getSupportedVersion() {
|
||||
return supportedVersion;
|
||||
public Integer getLatestSupportedVersion() {
|
||||
return latestSupportedVersion;
|
||||
}
|
||||
|
||||
public boolean isVaultOlderThanSoftware() {
|
||||
return detectedVersion == null || detectedVersion < supportedVersion;
|
||||
return detectedVersion == null || detectedVersion < latestSupportedVersion;
|
||||
}
|
||||
|
||||
public boolean isSoftwareOlderThanVault() {
|
||||
return detectedVersion > supportedVersion;
|
||||
return detectedVersion > latestSupportedVersion;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
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 Integer CURRENT_VAULT_VERSION = 3;
|
||||
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;
|
||||
|
||||
@@ -27,7 +27,12 @@ public class CryptoEngineModule {
|
||||
@Provides
|
||||
public SecureRandom provideSecureRandom() {
|
||||
try {
|
||||
return SecureRandom.getInstanceStrong();
|
||||
// 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);
|
||||
}
|
||||
|
||||
@@ -9,23 +9,23 @@
|
||||
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.MessageDigest;
|
||||
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.apache.commons.lang3.ArrayUtils;
|
||||
import org.cryptomator.common.LazyInitializer;
|
||||
import org.cryptomator.crypto.engine.Cryptor;
|
||||
import org.cryptomator.crypto.engine.FileContentCryptor;
|
||||
@@ -35,6 +35,7 @@ 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 {
|
||||
|
||||
@@ -82,14 +83,15 @@ class CryptorImpl implements Cryptor {
|
||||
|
||||
@Override
|
||||
public void randomizeMasterkey() {
|
||||
final byte[] randomBytes = new byte[KEYLENGTH_IN_BYTES];
|
||||
try {
|
||||
randomSource.nextBytes(randomBytes);
|
||||
encryptionKey = new SecretKeySpec(randomBytes, ENCRYPTION_ALG);
|
||||
randomSource.nextBytes(randomBytes);
|
||||
macKey = new SecretKeySpec(randomBytes, ENCRYPTION_ALG);
|
||||
} finally {
|
||||
Arrays.fill(randomBytes, (byte) 0x00);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,12 +101,16 @@ class CryptorImpl implements Cryptor {
|
||||
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 (keyFile.getVersion() != CURRENT_VAULT_VERSION || ArrayUtils.isEmpty(keyFile.getVersionMac())) {
|
||||
if (!SUPPORTED_VAULT_VERSIONS.contains(keyFile.getVersion())) {
|
||||
throw new UnsupportedVaultFormatException(keyFile.getVersion(), CURRENT_VAULT_VERSION);
|
||||
}
|
||||
|
||||
@@ -112,12 +118,13 @@ class CryptorImpl implements Cryptor {
|
||||
try {
|
||||
final SecretKey kek = new SecretKeySpec(kekBytes, ENCRYPTION_ALG);
|
||||
this.macKey = AesKeyWrap.unwrap(kek, keyFile.getMacMasterKey(), MAC_ALG);
|
||||
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);
|
||||
}
|
||||
// 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();
|
||||
|
||||
@@ -11,12 +11,14 @@ package org.cryptomator.crypto.engine.impl;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.ShortBufferException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
@@ -36,13 +38,13 @@ class FileHeaderPayload implements Destroyable {
|
||||
private final SecretKey contentKey;
|
||||
|
||||
public FileHeaderPayload(SecureRandom randomSource) {
|
||||
filesize = 0;
|
||||
final byte[] contentKey = new byte[CONTENT_KEY_LEN];
|
||||
this.filesize = 0;
|
||||
try {
|
||||
randomSource.nextBytes(contentKey);
|
||||
this.contentKey = new SecretKeySpec(contentKey, AES);
|
||||
} finally {
|
||||
Arrays.fill(contentKey, (byte) 0x00);
|
||||
KeyGenerator keyGen = KeyGenerator.getInstance(AES);
|
||||
keyGen.init(CONTENT_KEY_LEN * Byte.SIZE, randomSource);
|
||||
this.contentKey = keyGen.generateKey();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("Hard-coded algorithm doesn't exist.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.crypto.AEADBadTagException;
|
||||
import javax.crypto.SecretKey;
|
||||
@@ -25,6 +26,8 @@ import org.cryptomator.siv.SivMode;
|
||||
class FilenameCryptorImpl implements FilenameCryptor {
|
||||
|
||||
private static final BaseNCodec BASE32 = new Base32();
|
||||
// https://tools.ietf.org/html/rfc4648#section-6
|
||||
private static final Pattern BASE32_PATTERN = Pattern.compile("([A-Z2-7]{8})*[A-Z2-7=]{8}");
|
||||
private static final ThreadLocal<MessageDigest> SHA1 = new ThreadLocalSha1();
|
||||
private static final ThreadLocal<SivMode> AES_SIV = new ThreadLocal<SivMode>() {
|
||||
@Override
|
||||
@@ -50,8 +53,8 @@ class FilenameCryptorImpl implements FilenameCryptor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEncryptedFilename(String ciphertextName) {
|
||||
return BASE32.isInAlphabet(ciphertextName);
|
||||
public Pattern encryptedNamePattern() {
|
||||
return BASE32_PATTERN;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -25,7 +25,7 @@ final class ThreadLocalAesCtrCipher {
|
||||
try {
|
||||
return Cipher.getInstance(AES_CTR);
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
|
||||
throw new IllegalStateException("Could not create MAC.", e);
|
||||
throw new IllegalStateException("Could not create Cipher.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -111,7 +111,8 @@ class BlockAlignedWritableFile implements WritableFile {
|
||||
currentBlockBuffer.clear();
|
||||
try (ReadableFile r = openReadable.get()) {
|
||||
r.position(physicalPosition);
|
||||
r.read(currentBlockBuffer);
|
||||
int numRead = r.read(currentBlockBuffer);
|
||||
assert numRead == currentBlockBuffer.position();
|
||||
}
|
||||
int advance = (int) (logicalPosition - physicalPosition);
|
||||
currentBlockBuffer.position(advance);
|
||||
|
||||
@@ -47,10 +47,10 @@ class CiphertextReader implements Callable<Void> {
|
||||
int bytesRead = -1;
|
||||
do {
|
||||
ByteBuffer ciphertext = ByteBuffer.allocate(READ_BUFFER_SIZE);
|
||||
file.read(ciphertext);
|
||||
ciphertext.flip();
|
||||
bytesRead = ciphertext.remaining();
|
||||
bytesRead = file.read(ciphertext);
|
||||
if (bytesRead > 0) {
|
||||
ciphertext.flip();
|
||||
assert bytesRead == ciphertext.remaining();
|
||||
decryptor.append(ciphertext);
|
||||
}
|
||||
} while (bytesRead > 0);
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package org.cryptomator.filesystem.crypto;
|
||||
|
||||
import static org.cryptomator.filesystem.crypto.Constants.DIR_PREFIX;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.MatchResult;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
import org.cryptomator.filesystem.ReadableFile;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
final class ConflictResolver {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ConflictResolver.class);
|
||||
private static final int UUID_FIRST_GROUP_STRLEN = 8;
|
||||
private static final int MAX_DIR_FILE_SIZE = 87; // "normal" file header has 88 bytes
|
||||
|
||||
private final Pattern encryptedNamePattern;
|
||||
private final Function<String, Optional<String>> nameDecryptor;
|
||||
private final Function<String, Optional<String>> nameEncryptor;
|
||||
|
||||
public ConflictResolver(Pattern encryptedNamePattern, Function<String, Optional<String>> nameDecryptor, Function<String, Optional<String>> nameEncryptor) {
|
||||
this.encryptedNamePattern = encryptedNamePattern;
|
||||
this.nameDecryptor = nameDecryptor;
|
||||
this.nameEncryptor = nameEncryptor;
|
||||
}
|
||||
|
||||
public File resolveIfNecessary(File file) {
|
||||
Matcher m = encryptedNamePattern.matcher(StringUtils.removeStart(file.name(), DIR_PREFIX));
|
||||
if (m.matches()) {
|
||||
// full match, use file as is
|
||||
return file;
|
||||
} else if (m.find(0)) {
|
||||
// partial match, might be conflicting
|
||||
return resolveConflict(file, m.toMatchResult());
|
||||
} else {
|
||||
// no match, file not relevant
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
private File resolveConflict(File conflictingFile, MatchResult matchResult) {
|
||||
String ciphertext = matchResult.group();
|
||||
boolean isDirectory = conflictingFile.name().startsWith(DIR_PREFIX);
|
||||
Optional<String> cleartext = nameDecryptor.apply(ciphertext);
|
||||
if (cleartext.isPresent()) {
|
||||
Folder folder = conflictingFile.parent().get();
|
||||
File canonicalFile = folder.file(isDirectory ? DIR_PREFIX + ciphertext : ciphertext);
|
||||
if (isDirectory && canonicalFile.exists() && isSameFileBasedOnSample(canonicalFile, conflictingFile, MAX_DIR_FILE_SIZE)) {
|
||||
// there must not be two directories pointing to the same directory id. In this case no human interaction is needed to resolve this conflict:
|
||||
conflictingFile.delete();
|
||||
return canonicalFile;
|
||||
} else {
|
||||
// conventional conflict detected! look for an alternative name:
|
||||
File alternativeFile;
|
||||
String conflictId;
|
||||
do {
|
||||
conflictId = createConflictId();
|
||||
String alternativeCleartext = cleartext.get() + " (Conflict " + conflictId + ")";
|
||||
String alternativeCiphertext = nameEncryptor.apply(alternativeCleartext).get();
|
||||
alternativeFile = folder.file(isDirectory ? DIR_PREFIX + alternativeCiphertext : alternativeCiphertext);
|
||||
} while (alternativeFile.exists());
|
||||
LOG.info("Detected conflict {}:\n{}\n{}", conflictId, canonicalFile, conflictingFile);
|
||||
conflictingFile.moveTo(alternativeFile);
|
||||
return alternativeFile;
|
||||
}
|
||||
} else {
|
||||
// not decryptable; false positive
|
||||
return conflictingFile;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSameFileBasedOnSample(File file1, File file2, int sampleSize) {
|
||||
try (ReadableFile r1 = file1.openReadable(); ReadableFile r2 = file2.openReadable()) {
|
||||
if (r1.size() != r2.size()) {
|
||||
return false;
|
||||
} else {
|
||||
ByteBuffer beginOfFile1 = ByteBuffer.allocate(sampleSize);
|
||||
ByteBuffer beginOfFile2 = ByteBuffer.allocate(sampleSize);
|
||||
r1.read(beginOfFile1);
|
||||
r2.read(beginOfFile2);
|
||||
beginOfFile1.flip();
|
||||
beginOfFile2.flip();
|
||||
return beginOfFile1.equals(beginOfFile2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String createConflictId() {
|
||||
return UUID.randomUUID().toString().substring(0, UUID_FIRST_GROUP_STRLEN);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,9 +8,9 @@ public final class Constants {
|
||||
static final String DATA_ROOT_DIR = "d";
|
||||
static final String ROOT_DIRECOTRY_ID = "";
|
||||
|
||||
static final String MASTERKEY_FILENAME = "masterkey.cryptomator";
|
||||
static final String MASTERKEY_BACKUP_FILENAME = "masterkey.cryptomator.bkup";
|
||||
public static final String MASTERKEY_FILENAME = "masterkey.cryptomator";
|
||||
public static final String MASTERKEY_BACKUP_FILENAME = "masterkey.cryptomator.bkup";
|
||||
|
||||
static final String DIR_SUFFIX = "_";
|
||||
static final String DIR_PREFIX = "0";
|
||||
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.filesystem.crypto;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.util.Optional;
|
||||
@@ -27,15 +25,23 @@ class CryptoFile extends CryptoNode implements File {
|
||||
|
||||
@Override
|
||||
protected Optional<String> encryptedName() {
|
||||
return parent().get().getDirectoryId().map(s -> s.getBytes(UTF_8)).map(parentDirId -> {
|
||||
return cryptor.getFilenameCryptor().encryptFilename(name(), parentDirId);
|
||||
});
|
||||
return parent().get().encryptChildName(name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadableFile openReadable() {
|
||||
boolean authenticate = !fileSystem().delegate().shouldSkipAuthentication(toString());
|
||||
return new CryptoReadableFile(cryptor.getFileContentCryptor(), forceGetPhysicalFile().openReadable(), authenticate, this::reportAuthError);
|
||||
ReadableFile physicalReadable = forceGetPhysicalFile().openReadable();
|
||||
boolean success = false;
|
||||
try {
|
||||
final ReadableFile result = new CryptoReadableFile(cryptor.getFileContentCryptor(), physicalReadable, authenticate, this::reportAuthError);
|
||||
success = true;
|
||||
return result;
|
||||
} finally {
|
||||
if (!success) {
|
||||
physicalReadable.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void reportAuthError() {
|
||||
@@ -47,7 +53,17 @@ class CryptoFile extends CryptoNode implements File {
|
||||
if (parent.folder(name).exists()) {
|
||||
throw new UncheckedIOException(new FileAlreadyExistsException(toString()));
|
||||
}
|
||||
return new CryptoWritableFile(cryptor.getFileContentCryptor(), forceGetPhysicalFile().openWritable());
|
||||
WritableFile physicalWrtiable = forceGetPhysicalFile().openWritable();
|
||||
boolean success = false;
|
||||
try {
|
||||
final WritableFile result = new CryptoWritableFile(cryptor.getFileContentCryptor(), physicalWrtiable);
|
||||
success = true;
|
||||
return result;
|
||||
} finally {
|
||||
if (!success) {
|
||||
physicalWrtiable.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.filesystem.crypto;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.cryptomator.filesystem.crypto.Constants.DIR_SUFFIX;
|
||||
import static org.cryptomator.filesystem.crypto.Constants.DIR_PREFIX;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.UncheckedIOException;
|
||||
@@ -18,36 +19,44 @@ import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.cryptomator.common.LazyInitializer;
|
||||
import org.cryptomator.common.WeakValuedCache;
|
||||
import org.cryptomator.common.streams.AutoClosingStream;
|
||||
import org.cryptomator.crypto.engine.CryptoException;
|
||||
import org.cryptomator.crypto.engine.Cryptor;
|
||||
import org.cryptomator.filesystem.Deleter;
|
||||
import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
import org.cryptomator.filesystem.Node;
|
||||
import org.cryptomator.io.FileContents;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
class CryptoFolder extends CryptoNode implements Folder {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CryptoFolder.class);
|
||||
private final WeakValuedCache<String, CryptoFolder> folders = WeakValuedCache.usingLoader(this::newFolder);
|
||||
private final WeakValuedCache<String, CryptoFile> files = WeakValuedCache.usingLoader(this::newFile);
|
||||
private final AtomicReference<String> directoryId = new AtomicReference<>();
|
||||
private final ConflictResolver conflictResolver;
|
||||
|
||||
public CryptoFolder(CryptoFolder parent, String name, Cryptor cryptor) {
|
||||
super(parent, name, cryptor);
|
||||
this.conflictResolver = new ConflictResolver(cryptor.getFilenameCryptor().encryptedNamePattern(), this::decryptChildName, this::encryptChildName);
|
||||
}
|
||||
|
||||
/* ======================= name + directory id ======================= */
|
||||
|
||||
@Override
|
||||
protected Optional<String> encryptedName() {
|
||||
if (parent().isPresent()) {
|
||||
return parent().get().getDirectoryId().map(s -> s.getBytes(UTF_8)).map(parentDirId -> {
|
||||
return cryptor.getFilenameCryptor().encryptFilename(name(), parentDirId) + DIR_SUFFIX;
|
||||
});
|
||||
return parent().get().encryptChildName(name()).map(s -> DIR_PREFIX + s);
|
||||
} else {
|
||||
return Optional.of(cryptor.getFilenameCryptor().encryptFilename(name()) + DIR_SUFFIX);
|
||||
return Optional.of(DIR_PREFIX + cryptor.getFilenameCryptor().encryptFilename(name()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,24 +81,60 @@ class CryptoFolder extends CryptoNode implements Folder {
|
||||
}));
|
||||
}
|
||||
|
||||
/* ======================= children ======================= */
|
||||
|
||||
@Override
|
||||
public Stream<? extends Node> children() {
|
||||
return Stream.concat(files(), folders());
|
||||
return AutoClosingStream.from(Stream.concat(files(), folders()));
|
||||
}
|
||||
|
||||
private Stream<File> nonConflictingFiles() {
|
||||
if (exists()) {
|
||||
final Stream<? extends File> files = physicalFolder().filter(Folder::exists).map(Folder::files).orElse(Stream.empty());
|
||||
return files.filter(containsEncryptedName()).map(conflictResolver::resolveIfNecessary).distinct();
|
||||
} else {
|
||||
throw new UncheckedIOException(new FileNotFoundException(format("Folder %s does not exist", this)));
|
||||
}
|
||||
}
|
||||
|
||||
private Predicate<File> containsEncryptedName() {
|
||||
final Pattern encryptedNamePattern = cryptor.getFilenameCryptor().encryptedNamePattern();
|
||||
return (File file) -> encryptedNamePattern.matcher(file.name()).find();
|
||||
}
|
||||
|
||||
Optional<String> decryptChildName(String ciphertextFileName) {
|
||||
return getDirectoryId().map(s -> s.getBytes(UTF_8)).map(dirId -> {
|
||||
try {
|
||||
return cryptor.getFilenameCryptor().decryptFilename(ciphertextFileName, dirId);
|
||||
} catch (CryptoException e) {
|
||||
LOG.warn("Filename decryption of {} failed: {}", ciphertextFileName, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Optional<String> encryptChildName(String cleartextFileName) {
|
||||
return getDirectoryId().map(s -> s.getBytes(UTF_8)).map(dirId -> {
|
||||
return cryptor.getFilenameCryptor().encryptFilename(cleartextFileName, dirId);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<CryptoFile> files() {
|
||||
assert forceGetPhysicalFolder().exists();
|
||||
return forceGetPhysicalFolder().files().map(File::name).filter(isEncryptedFileName()).map(this::decryptChildFileName).map(this::file);
|
||||
return nonConflictingFiles().map(File::name).filter(startsWithDirPrefix().negate()).map(this::decryptChildName).filter(Optional::isPresent).map(Optional::get).map(this::file);
|
||||
}
|
||||
|
||||
private Predicate<String> isEncryptedFileName() {
|
||||
return (String name) -> !name.endsWith(DIR_SUFFIX) && cryptor.getFilenameCryptor().isEncryptedFilename(name);
|
||||
@Override
|
||||
public Stream<CryptoFolder> folders() {
|
||||
return nonConflictingFiles().map(File::name).filter(startsWithDirPrefix()).map(this::removeDirPrefix).map(this::decryptChildName).filter(Optional::isPresent).map(Optional::get).map(this::folder);
|
||||
}
|
||||
|
||||
private String decryptChildFileName(String encryptedFileName) {
|
||||
final byte[] dirId = getDirectoryId().get().getBytes(UTF_8);
|
||||
return cryptor.getFilenameCryptor().decryptFilename(encryptedFileName, dirId);
|
||||
private Predicate<String> startsWithDirPrefix() {
|
||||
return (String encryptedFolderName) -> StringUtils.startsWith(encryptedFolderName, DIR_PREFIX);
|
||||
}
|
||||
|
||||
private String removeDirPrefix(String encryptedFolderName) {
|
||||
return StringUtils.removeStart(encryptedFolderName, DIR_PREFIX);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -97,35 +142,21 @@ class CryptoFolder extends CryptoNode implements Folder {
|
||||
return files.get(name);
|
||||
}
|
||||
|
||||
public CryptoFile newFile(String name) {
|
||||
return new CryptoFile(this, name, cryptor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<CryptoFolder> folders() {
|
||||
assert forceGetPhysicalFolder().exists();
|
||||
return forceGetPhysicalFolder().files().map(File::name).filter(isEncryptedDirectoryName()).map(this::decryptChildFolderName).map(this::folder);
|
||||
}
|
||||
|
||||
private Predicate<String> isEncryptedDirectoryName() {
|
||||
return (String name) -> name.endsWith(DIR_SUFFIX) && cryptor.getFilenameCryptor().isEncryptedFilename(StringUtils.removeEnd(name, DIR_SUFFIX));
|
||||
}
|
||||
|
||||
private String decryptChildFolderName(String encryptedFolderName) {
|
||||
final byte[] dirId = getDirectoryId().get().getBytes(UTF_8);
|
||||
final String ciphertext = StringUtils.removeEnd(encryptedFolderName, DIR_SUFFIX);
|
||||
return cryptor.getFilenameCryptor().decryptFilename(ciphertext, dirId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CryptoFolder folder(String name) {
|
||||
return folders.get(name);
|
||||
}
|
||||
|
||||
public CryptoFolder newFolder(String name) {
|
||||
private CryptoFile newFile(String name) {
|
||||
return new CryptoFile(this, name, cryptor);
|
||||
}
|
||||
|
||||
private CryptoFolder newFolder(String name) {
|
||||
return new CryptoFolder(this, name, cryptor);
|
||||
}
|
||||
|
||||
/* ======================= create/move/delete ======================= */
|
||||
|
||||
@Override
|
||||
public void create() {
|
||||
parent.create();
|
||||
@@ -160,8 +191,8 @@ class CryptoFolder extends CryptoNode implements Folder {
|
||||
assert target.parent().isPresent() : "Target can not be root, thus has a parent";
|
||||
|
||||
// prepare target:
|
||||
target.parent().get().create();
|
||||
target.delete();
|
||||
target.parent().get().create();
|
||||
|
||||
// perform the actual move:
|
||||
final File dirFile = forceGetPhysicalFile();
|
||||
@@ -175,7 +206,7 @@ class CryptoFolder extends CryptoNode implements Folder {
|
||||
// cut all ties:
|
||||
this.invalidateDirectoryIdsRecursively();
|
||||
|
||||
assert!exists();
|
||||
assert !exists();
|
||||
assert target.exists();
|
||||
}
|
||||
|
||||
@@ -186,7 +217,12 @@ class CryptoFolder extends CryptoNode implements Folder {
|
||||
return;
|
||||
}
|
||||
Deleter.deleteContent(this);
|
||||
forceGetPhysicalFolder().delete();
|
||||
Folder physicalFolder = forceGetPhysicalFolder();
|
||||
physicalFolder.delete();
|
||||
Folder physicalFolderParent = physicalFolder.parent().get();
|
||||
if (physicalFolderParent.exists() && physicalFolderParent.folders().count() == 0) {
|
||||
physicalFolderParent.delete();
|
||||
}
|
||||
forceGetPhysicalFile().delete();
|
||||
invalidateDirectoryIdsRecursively();
|
||||
}
|
||||
|
||||
@@ -104,8 +104,8 @@ abstract class CryptoNode implements Node {
|
||||
if (obj instanceof CryptoNode) {
|
||||
CryptoNode other = (CryptoNode) obj;
|
||||
return this.getClass() == other.getClass() //
|
||||
&& (this.parent == null && other.parent == null || this.parent.equals(other.parent)) //
|
||||
&& (this.name == null && other.name == null || this.name.equals(other.name));
|
||||
&& (this.parent == null && other.parent == null || this.parent != null && this.parent.equals(other.parent)) //
|
||||
&& (this.name == null && other.name == null || this.name != null && this.name.equals(other.name));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,10 @@ class CryptoReadableFile implements ReadableFile {
|
||||
this.authenticate = authenticate;
|
||||
this.onAuthError = onAuthError;
|
||||
file.position(0);
|
||||
file.read(header);
|
||||
int headerBytesRead = file.read(header);
|
||||
if (headerBytesRead != header.capacity()) {
|
||||
throw new IllegalArgumentException("File too short to contain a header.");
|
||||
}
|
||||
header.flip();
|
||||
this.position(0);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.codec.binary.Base32;
|
||||
import org.apache.commons.codec.binary.BaseNCodec;
|
||||
@@ -19,6 +20,7 @@ import org.apache.commons.codec.binary.BaseNCodec;
|
||||
class NoFilenameCryptor implements FilenameCryptor {
|
||||
|
||||
private static final BaseNCodec BASE32 = new Base32();
|
||||
private static final Pattern WILDCARD_PATTERN = Pattern.compile(".*");
|
||||
private static final ThreadLocal<MessageDigest> SHA1 = new ThreadLocalSha1();
|
||||
|
||||
@Override
|
||||
@@ -29,8 +31,8 @@ class NoFilenameCryptor implements FilenameCryptor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEncryptedFilename(String ciphertextName) {
|
||||
return true;
|
||||
public Pattern encryptedNamePattern() {
|
||||
return WILDCARD_PATTERN;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -14,26 +14,27 @@ import org.cryptomator.crypto.engine.Cryptor;
|
||||
import org.cryptomator.crypto.engine.InvalidPassphraseException;
|
||||
import org.cryptomator.crypto.engine.UnsupportedVaultFormatException;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
public class CryptorImplTest {
|
||||
|
||||
@Test
|
||||
public void testMasterkeyDecryptionWithCorrectPassphrase() throws IOException {
|
||||
final String testMasterKey = "{\"version\":3,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," //
|
||||
final String testMasterKey = "{\"version\":4,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," //
|
||||
+ "\"primaryMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," //
|
||||
+ "\"hmacMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," //
|
||||
+ "\"versionMac\":\"iUmRRHITuyJsJbVNqGNw+82YQ4A3Rma7j/y1v0DCVLA=\"}";
|
||||
+ "\"versionMac\":\"Z9J8Uc5K1f7YKckLUFpXG39NHK1qUjzadw5nvOqvfok=\"}";
|
||||
final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl();
|
||||
cryptor.readKeysFromMasterkeyFile(testMasterKey.getBytes(), "asd");
|
||||
}
|
||||
|
||||
@Test(expected = InvalidPassphraseException.class)
|
||||
public void testMasterkeyDecryptionWithWrongPassphrase() throws IOException {
|
||||
final String testMasterKey = "{\"version\":3,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," //
|
||||
final String testMasterKey = "{\"version\":4,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," //
|
||||
+ "\"primaryMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," //
|
||||
+ "\"hmacMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," //
|
||||
+ "\"versionMac\":\"iUmRRHITuyJsJbVNqGNw+82YQ4A3Rma7j/y1v0DCVLA=\"}";
|
||||
+ "\"versionMac\":\"Z9J8Uc5K1f7YKckLUFpXG39NHK1qUjzadw5nvOqvfok=\"}";
|
||||
final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl();
|
||||
cryptor.readKeysFromMasterkeyFile(testMasterKey.getBytes(), "qwe");
|
||||
}
|
||||
@@ -43,11 +44,12 @@ public class CryptorImplTest {
|
||||
final String testMasterKey = "{\"version\":-1,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," //
|
||||
+ "\"primaryMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," //
|
||||
+ "\"hmacMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," //
|
||||
+ "\"versionMac\":\"iUmRRHITuyJsJbVNqGNw+82YQ4A3Rma7j/y1v0DCVLA=\"}";
|
||||
+ "\"versionMac\":\"Z9J8Uc5K1f7YKckLUFpXG39NHK1qUjzadw5nvOqvfok=\"}";
|
||||
final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl();
|
||||
cryptor.readKeysFromMasterkeyFile(testMasterKey.getBytes(), "asd");
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test(expected = UnsupportedVaultFormatException.class)
|
||||
public void testMasterkeyDecryptionWithMissingVersionMac() throws IOException {
|
||||
final String testMasterKey = "{\"version\":3,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," //
|
||||
@@ -57,25 +59,27 @@ public class CryptorImplTest {
|
||||
cryptor.readKeysFromMasterkeyFile(testMasterKey.getBytes(), "asd");
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test(expected = UnsupportedVaultFormatException.class)
|
||||
public void testMasterkeyDecryptionWithWrongVersionMac() throws IOException {
|
||||
final String testMasterKey = "{\"version\":3,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," //
|
||||
final String testMasterKey = "{\"version\":4,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," //
|
||||
+ "\"primaryMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," //
|
||||
+ "\"hmacMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," //
|
||||
+ "\"versionMac\":\"iUmRRHITuyJsJbVNqGNw+82YQ4A3Rma7j/y1v0DCVLa=\"}";
|
||||
+ "\"versionMac\":\"z9J8Uc5K1f7YKckLUFpXG39NHK1qUjzadw5nvOqvfoK=\"}";
|
||||
final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl();
|
||||
cryptor.readKeysFromMasterkeyFile(testMasterKey.getBytes(), "asd");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMasterkeyEncryption() throws IOException {
|
||||
final String expectedMasterKey = "{\"version\":3,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":16384,\"scryptBlockSize\":8," //
|
||||
final String expectedMasterKey = "{\"version\":4,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":16384,\"scryptBlockSize\":8," //
|
||||
+ "\"primaryMasterKey\":\"BJPIq5pvhN24iDtPJLMFPLaVJWdGog9k4n0P03j4ru+ivbWY9OaRGQ==\"," //
|
||||
+ "\"hmacMasterKey\":\"BJPIq5pvhN24iDtPJLMFPLaVJWdGog9k4n0P03j4ru+ivbWY9OaRGQ==\"," //
|
||||
+ "\"versionMac\":\"iUmRRHITuyJsJbVNqGNw+82YQ4A3Rma7j/y1v0DCVLA=\"}";
|
||||
+ "\"versionMac\":\"Z9J8Uc5K1f7YKckLUFpXG39NHK1qUjzadw5nvOqvfok=\"}";
|
||||
final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl();
|
||||
cryptor.randomizeMasterkey();
|
||||
final byte[] masterkeyFile = cryptor.writeKeysToMasterkeyFile("asd");
|
||||
System.out.println(new String(masterkeyFile));
|
||||
Assert.assertArrayEquals(expectedMasterKey.getBytes(), masterkeyFile);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
package org.cryptomator.filesystem.crypto;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.codec.binary.Base32;
|
||||
import org.apache.commons.codec.binary.BaseNCodec;
|
||||
import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
import org.cryptomator.filesystem.ReadableFile;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
public class ConflictResolverTest {
|
||||
|
||||
private ConflictResolver conflictResolver;
|
||||
private Folder folder;
|
||||
private File canonicalFile;
|
||||
private File canonicalFolder;
|
||||
private File conflictingFile;
|
||||
private File conflictingFolder;
|
||||
private File resolved;
|
||||
private File unrelatedFile;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
Pattern base32Pattern = Pattern.compile("([A-Z0-9]{8})*[A-Z0-9=]{8}");
|
||||
BaseNCodec base32 = new Base32();
|
||||
Function<String, Optional<String>> decode = (s) -> Optional.of(new String(base32.decode(s), StandardCharsets.UTF_8));
|
||||
Function<String, Optional<String>> encode = (s) -> Optional.of(base32.encodeAsString(s.getBytes(StandardCharsets.UTF_8)));
|
||||
conflictResolver = new ConflictResolver(base32Pattern, decode, encode);
|
||||
|
||||
folder = Mockito.mock(Folder.class);
|
||||
canonicalFile = Mockito.mock(File.class);
|
||||
canonicalFolder = Mockito.mock(File.class);
|
||||
conflictingFile = Mockito.mock(File.class);
|
||||
conflictingFolder = Mockito.mock(File.class);
|
||||
resolved = Mockito.mock(File.class);
|
||||
unrelatedFile = Mockito.mock(File.class);
|
||||
|
||||
String canonicalFileName = encode.apply("test name").get();
|
||||
String canonicalFolderName = Constants.DIR_PREFIX + canonicalFileName;
|
||||
String conflictingFileName = canonicalFileName + " (version 2)";
|
||||
String conflictingFolderName = canonicalFolderName + " (version 2)";
|
||||
String unrelatedName = "notBa$e32!";
|
||||
|
||||
Mockito.when(canonicalFile.name()).thenReturn(canonicalFileName);
|
||||
Mockito.when(canonicalFolder.name()).thenReturn(canonicalFolderName);
|
||||
Mockito.when(conflictingFile.name()).thenReturn(conflictingFileName);
|
||||
Mockito.when(conflictingFolder.name()).thenReturn(conflictingFolderName);
|
||||
Mockito.when(unrelatedFile.name()).thenReturn(unrelatedName);
|
||||
|
||||
Mockito.when(canonicalFile.exists()).thenReturn(true);
|
||||
Mockito.when(canonicalFolder.exists()).thenReturn(true);
|
||||
Mockito.when(conflictingFile.exists()).thenReturn(true);
|
||||
Mockito.when(conflictingFolder.exists()).thenReturn(true);
|
||||
Mockito.when(unrelatedFile.exists()).thenReturn(true);
|
||||
|
||||
Mockito.doReturn(Optional.of(folder)).when(canonicalFile).parent();
|
||||
Mockito.doReturn(Optional.of(folder)).when(canonicalFolder).parent();
|
||||
Mockito.doReturn(Optional.of(folder)).when(conflictingFile).parent();
|
||||
Mockito.doReturn(Optional.of(folder)).when(conflictingFolder).parent();
|
||||
Mockito.doReturn(Optional.of(folder)).when(unrelatedFile).parent();
|
||||
|
||||
Mockito.when(folder.file(Mockito.startsWith(canonicalFileName.substring(0, 8)))).thenReturn(resolved);
|
||||
Mockito.when(folder.file(Mockito.startsWith(canonicalFolderName.substring(0, 8)))).thenReturn(resolved);
|
||||
Mockito.when(folder.file(canonicalFileName)).thenReturn(canonicalFile);
|
||||
Mockito.when(folder.file(canonicalFolderName)).thenReturn(canonicalFolder);
|
||||
Mockito.when(folder.file(conflictingFileName)).thenReturn(conflictingFile);
|
||||
Mockito.when(folder.file(conflictingFolderName)).thenReturn(conflictingFolder);
|
||||
Mockito.when(folder.file(unrelatedName)).thenReturn(unrelatedFile);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanonicalName() {
|
||||
File result = conflictResolver.resolveIfNecessary(canonicalFile);
|
||||
Assert.assertSame(canonicalFile, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnrelatedName() {
|
||||
File result = conflictResolver.resolveIfNecessary(unrelatedFile);
|
||||
Assert.assertSame(unrelatedFile, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConflictingFile() {
|
||||
File result = conflictResolver.resolveIfNecessary(conflictingFile);
|
||||
Mockito.verify(conflictingFile).moveTo(resolved);
|
||||
Assert.assertSame(resolved, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConflictingFileIfCanonicalDoesntExist() {
|
||||
Mockito.when(canonicalFile.exists()).thenReturn(false);
|
||||
File result = conflictResolver.resolveIfNecessary(conflictingFile);
|
||||
Mockito.verify(conflictingFile).moveTo(resolved);
|
||||
Assert.assertSame(resolved, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConflictingFolderWithDifferentId() {
|
||||
ReadableFile directoryId1 = Mockito.mock(ReadableFile.class);
|
||||
ReadableFile directoryId2 = Mockito.mock(ReadableFile.class);
|
||||
Mockito.when(canonicalFolder.openReadable()).thenReturn(directoryId1);
|
||||
Mockito.when(conflictingFolder.openReadable()).thenReturn(directoryId2);
|
||||
Mockito.when(directoryId1.read(Mockito.any())).thenAnswer(new FillBufferAnswer("id1"));
|
||||
Mockito.when(directoryId2.read(Mockito.any())).thenAnswer(new FillBufferAnswer("id2"));
|
||||
|
||||
File result = conflictResolver.resolveIfNecessary(conflictingFolder);
|
||||
Mockito.verify(conflictingFolder).moveTo(resolved);
|
||||
Assert.assertSame(resolved, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConflictingFolderWithSameId() {
|
||||
ReadableFile directoryId1 = Mockito.mock(ReadableFile.class);
|
||||
ReadableFile directoryId2 = Mockito.mock(ReadableFile.class);
|
||||
Mockito.when(canonicalFolder.openReadable()).thenReturn(directoryId1);
|
||||
Mockito.when(conflictingFolder.openReadable()).thenReturn(directoryId2);
|
||||
Mockito.when(directoryId1.read(Mockito.any())).thenAnswer(new FillBufferAnswer("id1"));
|
||||
Mockito.when(directoryId2.read(Mockito.any())).thenAnswer(new FillBufferAnswer("id1"));
|
||||
|
||||
File result = conflictResolver.resolveIfNecessary(conflictingFolder);
|
||||
Mockito.verify(conflictingFolder).delete();
|
||||
Assert.assertSame(canonicalFolder, result);
|
||||
}
|
||||
|
||||
private static class FillBufferAnswer implements Answer<Integer> {
|
||||
|
||||
private final byte[] content;
|
||||
private int bytesRead = 0;
|
||||
|
||||
public FillBufferAnswer(String content) {
|
||||
this.content = content.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer answer(InvocationOnMock invocation) throws Throwable {
|
||||
if (bytesRead >= content.length) {
|
||||
bytesRead = 0;
|
||||
return -1;
|
||||
} else {
|
||||
ByteBuffer buf = invocation.getArgumentAt(0, ByteBuffer.class);
|
||||
int delta = Math.min(content.length - bytesRead, buf.remaining());
|
||||
buf.put(content, bytesRead, delta);
|
||||
bytesRead += delta;
|
||||
return content.length;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,14 +8,15 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.filesystem.crypto;
|
||||
|
||||
import static org.cryptomator.filesystem.FileSystemVisitor.fileSystemVisitor;
|
||||
import static org.hamcrest.Matchers.both;
|
||||
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
||||
import static org.hamcrest.Matchers.lessThanOrEqualTo;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.cryptomator.crypto.engine.Cryptor;
|
||||
import org.cryptomator.crypto.engine.NoCryptor;
|
||||
@@ -37,7 +38,6 @@ public class CryptoFileSystemTest {
|
||||
final FileSystem physicalFs = new InMemoryFileSystem();
|
||||
final Folder physicalDataRoot = physicalFs.folder("d");
|
||||
final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor, Mockito.mock(CryptoFileSystemDelegate.class), "foo");
|
||||
fs.create();
|
||||
|
||||
// add another encrypted folder:
|
||||
final Folder fooFolder = fs.folder("foo");
|
||||
@@ -47,8 +47,24 @@ public class CryptoFileSystemTest {
|
||||
fooBarFolder.create();
|
||||
Assert.assertTrue(fooFolder.exists());
|
||||
Assert.assertTrue(fooBarFolder.exists());
|
||||
Assert.assertEquals(3, countDataFolders(physicalDataRoot)); // parent +
|
||||
// foo + bar
|
||||
Assert.assertEquals(3, countDataFolders(physicalDataRoot)); // parent + foo + bar
|
||||
}
|
||||
|
||||
@Test(timeout = 1000)
|
||||
public void testDirectoryDeletion() throws UncheckedIOException, IOException {
|
||||
// mock stuff and prepare crypto FS:
|
||||
final Cryptor cryptor = new NoCryptor();
|
||||
final FileSystem physicalFs = new InMemoryFileSystem();
|
||||
final Folder physicalDataRoot = physicalFs.folder("d");
|
||||
final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor, Mockito.mock(CryptoFileSystemDelegate.class), "foo");
|
||||
|
||||
// create and delete folders:
|
||||
fs.folder("foo").folder("bar").folder("baz").create();
|
||||
Assert.assertEquals(4, countDataFolders(physicalDataRoot)); // root + foo + bar + baz
|
||||
Assert.assertThat(physicalDataRoot.folders().count(), both(greaterThanOrEqualTo(1l)).and(lessThanOrEqualTo(4l))); // parent folders of the 4 folders
|
||||
fs.folder("foo").delete();
|
||||
Assert.assertEquals(1, countDataFolders(physicalDataRoot)); // just root
|
||||
Assert.assertEquals(1, physicalDataRoot.folders().count()); // just the parent of root
|
||||
}
|
||||
|
||||
@Test(timeout = 2000)
|
||||
@@ -204,22 +220,10 @@ public class CryptoFileSystemTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return number of folders on second level inside the given dataRoot
|
||||
* folder.
|
||||
* @return number of folders on second level inside the given dataRoot folder.
|
||||
*/
|
||||
private static int countDataFolders(Folder dataRoot) {
|
||||
final AtomicInteger num = new AtomicInteger();
|
||||
fileSystemVisitor() //
|
||||
.afterFolder(folder -> {
|
||||
final Folder parent = folder.parent().get();
|
||||
final Folder parentOfParent = parent.parent().orElse(null);
|
||||
if (parentOfParent != null && parentOfParent.equals(dataRoot)) {
|
||||
num.incrementAndGet();
|
||||
}
|
||||
}) //
|
||||
.withMaxDepth(2) //
|
||||
.visit(dataRoot);
|
||||
return num.get();
|
||||
return (int) dataRoot.folders().flatMap(Folder::folders).count();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.1.2</version>
|
||||
</parent>
|
||||
<artifactId>filesystem-inmemory</artifactId>
|
||||
<name>Cryptomator filesystem: In-memory mock</name>
|
||||
|
||||
@@ -13,6 +13,7 @@ import java.io.UncheckedIOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
|
||||
@@ -30,11 +31,16 @@ class InMemoryFile extends InMemoryNode implements File {
|
||||
static final double GROWTH_RATE = 1.4;
|
||||
|
||||
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
|
||||
private volatile ByteBuffer content = ByteBuffer.allocate(INITIAL_SIZE);
|
||||
private final AtomicReference<ByteBuffer> content = new AtomicReference<>(createNewEmptyByteBuffer());
|
||||
|
||||
public InMemoryFile(InMemoryFolder parent, String name, Instant lastModified, Instant creationTime) {
|
||||
super(parent, name, lastModified, creationTime);
|
||||
content.flip();
|
||||
}
|
||||
|
||||
static ByteBuffer createNewEmptyByteBuffer() {
|
||||
final ByteBuffer buf = ByteBuffer.allocate(INITIAL_SIZE);
|
||||
buf.flip();
|
||||
return buf;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -47,9 +53,9 @@ class InMemoryFile extends InMemoryNode implements File {
|
||||
}
|
||||
|
||||
private void internalMoveTo(InMemoryFile destination) {
|
||||
this.content.rewind();
|
||||
this.content.get().rewind();
|
||||
destination.create();
|
||||
destination.content = this.content;
|
||||
destination.content.set(this.content.getAndSet(createNewEmptyByteBuffer()));
|
||||
this.delete();
|
||||
}
|
||||
|
||||
@@ -62,7 +68,7 @@ class InMemoryFile extends InMemoryNode implements File {
|
||||
final ReadLock readLock = lock.readLock();
|
||||
readLock.lock();
|
||||
try {
|
||||
final ReadableFile result = new InMemoryReadableFile(this::getContent, readLock);
|
||||
final ReadableFile result = new InMemoryReadableFile(content::get, readLock);
|
||||
success = true;
|
||||
return result;
|
||||
} finally {
|
||||
@@ -79,7 +85,7 @@ class InMemoryFile extends InMemoryNode implements File {
|
||||
writeLock.lock();
|
||||
try {
|
||||
create();
|
||||
final WritableFile result = new InMemoryWritableFile(this::getContent, this::setContent, writeLock);
|
||||
final WritableFile result = new InMemoryWritableFile(content::get, content::set, writeLock);
|
||||
success = true;
|
||||
return result;
|
||||
} finally {
|
||||
@@ -97,7 +103,7 @@ class InMemoryFile extends InMemoryNode implements File {
|
||||
throw new UncheckedIOException(new FileAlreadyExistsException(k));
|
||||
} else {
|
||||
if (v == null) {
|
||||
assert!content.hasRemaining();
|
||||
assert!content.get().hasRemaining();
|
||||
this.creationTime = Instant.now();
|
||||
}
|
||||
this.lastModified = Instant.now();
|
||||
@@ -106,18 +112,9 @@ class InMemoryFile extends InMemoryNode implements File {
|
||||
});
|
||||
}
|
||||
|
||||
private ByteBuffer getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
private void setContent(ByteBuffer content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
content = ByteBuffer.allocate(INITIAL_SIZE);
|
||||
content.flip();
|
||||
content.set(createNewEmptyByteBuffer());
|
||||
final InMemoryFolder parent = parent().get();
|
||||
parent.existingChildren.computeIfPresent(this.name(), (k, v) -> {
|
||||
// returning null removes the entry.
|
||||
|
||||
@@ -86,8 +86,8 @@ abstract class InMemoryNode implements Node {
|
||||
if (obj instanceof InMemoryNode) {
|
||||
InMemoryNode other = (InMemoryNode) obj;
|
||||
return this.getClass() == other.getClass() //
|
||||
&& (this.parent == null && other.parent == null || this.parent.equals(other.parent)) //
|
||||
&& (this.name == null && other.name == null || this.name.equals(other.name));
|
||||
&& (this.parent == null && other.parent == null || this.parent != null && this.parent.equals(other.parent)) //
|
||||
&& (this.name == null && other.name == null || this.name != null && this.name.equals(other.name));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ package org.cryptomator.filesystem.inmem;
|
||||
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -20,8 +22,8 @@ class InMemoryReadableFile implements ReadableFile {
|
||||
|
||||
private final Supplier<ByteBuffer> contentGetter;
|
||||
private final ReadLock readLock;
|
||||
private boolean open = true;
|
||||
private volatile int position = 0;
|
||||
private final AtomicInteger position = new AtomicInteger();
|
||||
private final AtomicBoolean open = new AtomicBoolean(true);
|
||||
|
||||
public InMemoryReadableFile(Supplier<ByteBuffer> contentGetter, ReadLock readLock) {
|
||||
this.contentGetter = contentGetter;
|
||||
@@ -30,19 +32,21 @@ class InMemoryReadableFile implements ReadableFile {
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return open;
|
||||
return open.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(ByteBuffer destination) throws UncheckedIOException {
|
||||
ByteBuffer source = contentGetter.get().asReadOnlyBuffer();
|
||||
if (position >= source.limit()) {
|
||||
int toBeCopied = destination.remaining();
|
||||
int pos = position.getAndAdd(toBeCopied);
|
||||
if (pos >= source.limit()) {
|
||||
return -1;
|
||||
} else {
|
||||
source.position(position);
|
||||
source.position(pos);
|
||||
assert source.hasRemaining();
|
||||
int numRead = ByteBuffers.copy(source, destination);
|
||||
this.position += numRead;
|
||||
assert numRead <= toBeCopied;
|
||||
return numRead;
|
||||
}
|
||||
}
|
||||
@@ -55,12 +59,12 @@ class InMemoryReadableFile implements ReadableFile {
|
||||
@Override
|
||||
public void position(long position) throws UncheckedIOException {
|
||||
assert position < Integer.MAX_VALUE : "Can not use that big in-memory files.";
|
||||
this.position = (int) position;
|
||||
this.position.set((int) position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws UncheckedIOException {
|
||||
open = false;
|
||||
open.set(false);
|
||||
readLock.unlock();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@ package org.cryptomator.filesystem.inmem;
|
||||
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
@@ -22,9 +25,9 @@ public class InMemoryWritableFile implements WritableFile {
|
||||
private final Supplier<ByteBuffer> contentGetter;
|
||||
private final Consumer<ByteBuffer> contentSetter;
|
||||
private final WriteLock writeLock;
|
||||
|
||||
private boolean open = true;
|
||||
private volatile int position = 0;
|
||||
private final AtomicInteger position = new AtomicInteger();
|
||||
private final AtomicBoolean open = new AtomicBoolean(true);
|
||||
private final ReentrantLock writingLock = new ReentrantLock();
|
||||
|
||||
public InMemoryWritableFile(Supplier<ByteBuffer> contentGetter, Consumer<ByteBuffer> contentSetter, WriteLock writeLock) {
|
||||
this.contentGetter = contentGetter;
|
||||
@@ -34,44 +37,65 @@ public class InMemoryWritableFile implements WritableFile {
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return open;
|
||||
return open.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void truncate() throws UncheckedIOException {
|
||||
contentSetter.accept(ByteBuffer.allocate(0));
|
||||
writingLock.lock();
|
||||
try {
|
||||
contentSetter.accept(InMemoryFile.createNewEmptyByteBuffer());
|
||||
position.set(0);
|
||||
} finally {
|
||||
writingLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int write(ByteBuffer source) throws UncheckedIOException {
|
||||
ByteBuffer destination = contentGetter.get();
|
||||
int oldFileSize = destination.limit();
|
||||
int requiredSize = position + source.remaining();
|
||||
int newFileSize = Math.max(oldFileSize, requiredSize);
|
||||
if (destination.capacity() < requiredSize) {
|
||||
ByteBuffer old = destination;
|
||||
old.clear();
|
||||
int newBufferSize = Math.max(requiredSize, (int) (destination.capacity() * InMemoryFile.GROWTH_RATE));
|
||||
destination = ByteBuffer.allocate(newBufferSize);
|
||||
ByteBuffers.copy(old, destination);
|
||||
contentSetter.accept(destination);
|
||||
writingLock.lock();
|
||||
try {
|
||||
ByteBuffer content = contentGetter.get();
|
||||
int prevLimit = content.limit();
|
||||
|
||||
int toBeCopied = source.remaining();
|
||||
int pos = position.getAndAdd(toBeCopied);
|
||||
int ourLimit = pos + toBeCopied;
|
||||
int newLimit = Math.max(prevLimit, ourLimit);
|
||||
|
||||
ByteBuffer destination = ensureCapacity(content, newLimit);
|
||||
destination.limit(newLimit).position(pos);
|
||||
return ByteBuffers.copy(source, destination);
|
||||
} finally {
|
||||
writingLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private ByteBuffer ensureCapacity(ByteBuffer buf, int limit) {
|
||||
assert writingLock.isHeldByCurrentThread();
|
||||
if (buf.capacity() < limit) {
|
||||
int oldPos = buf.position();
|
||||
buf.clear();
|
||||
int newBufferSize = Math.max(limit, (int) (buf.capacity() * InMemoryFile.GROWTH_RATE));
|
||||
ByteBuffer newBuf = ByteBuffer.allocate(newBufferSize);
|
||||
ByteBuffers.copy(buf, newBuf);
|
||||
newBuf.limit(limit).position(oldPos);
|
||||
contentSetter.accept(newBuf);
|
||||
return newBuf;
|
||||
} else {
|
||||
return buf;
|
||||
}
|
||||
destination.limit(newFileSize);
|
||||
destination.position(position);
|
||||
int numWritten = ByteBuffers.copy(source, destination);
|
||||
this.position += numWritten;
|
||||
return numWritten;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void position(long position) throws UncheckedIOException {
|
||||
assert position < Integer.MAX_VALUE : "Can not use that big in-memory files.";
|
||||
this.position = (int) position;
|
||||
this.position.set((int) position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws UncheckedIOException {
|
||||
open = false;
|
||||
open.set(false);
|
||||
writeLock.unlock();
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.1.2</version>
|
||||
</parent>
|
||||
<artifactId>filesystem-invariants-tests</artifactId>
|
||||
<name>Cryptomator filesystem: Invariants tests</name>
|
||||
@@ -20,6 +20,10 @@
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>filesystem-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>filesystem-charsets</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>filesystem-crypto</artifactId>
|
||||
|
||||
@@ -4,11 +4,13 @@ import static org.cryptomator.common.test.TempFilesRemovedOnShutdown.createTempD
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.text.Normalizer.Form;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.cryptomator.filesystem.FileSystem;
|
||||
import org.cryptomator.filesystem.charsets.NormalizedNameFileSystem;
|
||||
import org.cryptomator.filesystem.crypto.CryptoEngineTestModule;
|
||||
import org.cryptomator.filesystem.crypto.CryptoFileSystemDelegate;
|
||||
import org.cryptomator.filesystem.crypto.CryptoFileSystemTestComponent;
|
||||
@@ -17,6 +19,7 @@ import org.cryptomator.filesystem.inmem.InMemoryFileSystem;
|
||||
import org.cryptomator.filesystem.invariants.FileSystemFactories.FileSystemFactory;
|
||||
import org.cryptomator.filesystem.nio.NioFileSystem;
|
||||
import org.cryptomator.filesystem.shortening.ShorteningFileSystem;
|
||||
import org.cryptomator.filesystem.stats.StatsFileSystem;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
class FileSystemFactories implements Iterable<FileSystemFactory> {
|
||||
@@ -28,10 +31,56 @@ class FileSystemFactories implements Iterable<FileSystemFactory> {
|
||||
public FileSystemFactories() {
|
||||
add("NioFileSystem", this::createNioFileSystem);
|
||||
add("InMemoryFileSystem", this::createInMemoryFileSystem);
|
||||
add("CryptoFileSystem(NioFileSystem)", this::createCryptoFileSystemNio);
|
||||
add("CryptoFileSystem(InMemoryFileSystem)", this::createCryptoFileSystemInMemory);
|
||||
add("ShorteningFileSystem(NioFileSystem)", this::createShorteningFileSystemNio);
|
||||
add("ShorteningFileSystem(InMemoryFileSystem)", this::createShorteningFileSystemInMemory);
|
||||
add("CryptoFileSystem > NioFileSystem", this::createCryptoFileSystemNio);
|
||||
add("CryptoFileSystem > InMemoryFileSystem", this::createCryptoFileSystemInMemory);
|
||||
add("ShorteningFileSystem > NioFileSystem", this::createShorteningFileSystemNio);
|
||||
add("ShorteningFileSystem > InMemoryFileSystem", this::createShorteningFileSystemInMemory);
|
||||
add("StatsFileSystem > NioFileSystem", this::createStatsFileSystemNio);
|
||||
add("StatsFileSystem > InMemoryFileSystem", this::createStatsFileSystemInMemory);
|
||||
add("NormalizingFileSystem > NioFileSystem", this::createNormalizingFileSystemNio);
|
||||
add("NormalizingFileSystem > InMemoryFileSystem", this::createNormalizingFileSystemInMemory);
|
||||
add("StatsFileSystem > NormalizingFileSystem > CryptoFileSystem > ShorteningFileSystem > InMemoryFileSystem", this::createCompoundFileSystemInMemory);
|
||||
add("StatsFileSystem > NormalizingFileSystem > CryptoFileSystem > ShorteningFileSystem > NioFileSystem", this::createCompoundFileSystemNio);
|
||||
}
|
||||
|
||||
private FileSystem createCryptoFileSystemInMemory() {
|
||||
return createCryptoFileSystem(createInMemoryFileSystem());
|
||||
}
|
||||
|
||||
private FileSystem createCryptoFileSystemNio() {
|
||||
return createCryptoFileSystem(createNioFileSystem());
|
||||
}
|
||||
|
||||
private FileSystem createShorteningFileSystemNio() {
|
||||
return createShorteningFileSystem(createNioFileSystem());
|
||||
}
|
||||
|
||||
private FileSystem createShorteningFileSystemInMemory() {
|
||||
return createShorteningFileSystem(createInMemoryFileSystem());
|
||||
}
|
||||
|
||||
private FileSystem createStatsFileSystemNio() {
|
||||
return createStatsFileSystem(createNioFileSystem());
|
||||
}
|
||||
|
||||
private FileSystem createStatsFileSystemInMemory() {
|
||||
return createStatsFileSystem(createInMemoryFileSystem());
|
||||
}
|
||||
|
||||
private FileSystem createNormalizingFileSystemNio() {
|
||||
return createNormalizingFileSystem(createInMemoryFileSystem());
|
||||
}
|
||||
|
||||
private FileSystem createNormalizingFileSystemInMemory() {
|
||||
return createNormalizingFileSystem(createInMemoryFileSystem());
|
||||
}
|
||||
|
||||
private FileSystem createCompoundFileSystemNio() {
|
||||
return createCompoundFileSystem(createNioFileSystem());
|
||||
}
|
||||
|
||||
private FileSystem createCompoundFileSystemInMemory() {
|
||||
return createCompoundFileSystem(createInMemoryFileSystem());
|
||||
}
|
||||
|
||||
private FileSystem createNioFileSystem() {
|
||||
@@ -46,25 +95,24 @@ class FileSystemFactories implements Iterable<FileSystemFactory> {
|
||||
return new InMemoryFileSystem();
|
||||
}
|
||||
|
||||
private FileSystem createCryptoFileSystemInMemory() {
|
||||
FileSystem delegate = createInMemoryFileSystem();
|
||||
private FileSystem createCompoundFileSystem(FileSystem delegate) {
|
||||
return createStatsFileSystem(createNormalizingFileSystem(createCryptoFileSystem(createShorteningFileSystem(delegate))));
|
||||
}
|
||||
|
||||
private FileSystem createStatsFileSystem(FileSystem delegate) {
|
||||
return new StatsFileSystem(delegate);
|
||||
}
|
||||
|
||||
private FileSystem createNormalizingFileSystem(FileSystem delegate) {
|
||||
return new NormalizedNameFileSystem(delegate, Form.NFC);
|
||||
}
|
||||
|
||||
private FileSystem createCryptoFileSystem(FileSystem delegate) {
|
||||
CRYPTO_FS_COMP.cryptoFileSystemFactory().initializeNew(delegate, "aPassphrase");
|
||||
return CRYPTO_FS_COMP.cryptoFileSystemFactory().unlockExisting(delegate, "aPassphrase", Mockito.mock(CryptoFileSystemDelegate.class));
|
||||
}
|
||||
|
||||
private FileSystem createCryptoFileSystemNio() {
|
||||
FileSystem delegate = createNioFileSystem();
|
||||
CRYPTO_FS_COMP.cryptoFileSystemFactory().initializeNew(delegate, "aPassphrase");
|
||||
return CRYPTO_FS_COMP.cryptoFileSystemFactory().unlockExisting(delegate, "aPassphrase", Mockito.mock(CryptoFileSystemDelegate.class));
|
||||
}
|
||||
|
||||
private FileSystem createShorteningFileSystemNio() {
|
||||
FileSystem delegate = createNioFileSystem();
|
||||
return new ShorteningFileSystem(delegate, "m", 3);
|
||||
}
|
||||
|
||||
private FileSystem createShorteningFileSystemInMemory() {
|
||||
FileSystem delegate = createInMemoryFileSystem();
|
||||
private FileSystem createShorteningFileSystem(FileSystem delegate) {
|
||||
return new ShorteningFileSystem(delegate, "m", 3);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.1.2</version>
|
||||
</parent>
|
||||
<artifactId>filesystem-nameshortening</artifactId>
|
||||
<name>Cryptomator filesystem: Name shortening layer</name>
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.cryptomator.filesystem.shortening;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
final class ConflictResolver {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ConflictResolver.class);
|
||||
private static final String LONG_NAME_FILE_EXT = ".lng";
|
||||
private static final Pattern BASE32_PATTERN = Pattern.compile("([A-Z0-9]{8})*[A-Z0-9=]{8}");
|
||||
private static final int UUID_FIRST_GROUP_STRLEN = 8;
|
||||
|
||||
private ConflictResolver() {
|
||||
}
|
||||
|
||||
public static File resolveConflictIfNecessary(File potentiallyConflictingFile, FilenameShortener shortener) {
|
||||
String shortName = potentiallyConflictingFile.name();
|
||||
String basename = StringUtils.removeEnd(shortName, LONG_NAME_FILE_EXT);
|
||||
Matcher matcher = BASE32_PATTERN.matcher(basename);
|
||||
if (shortName.endsWith(LONG_NAME_FILE_EXT) && matcher.matches()) {
|
||||
// no conflict.
|
||||
return potentiallyConflictingFile;
|
||||
} else if (shortName.endsWith(LONG_NAME_FILE_EXT) && matcher.find(0)) {
|
||||
String canonicalShortName = matcher.group() + LONG_NAME_FILE_EXT;
|
||||
return resolveConflict(potentiallyConflictingFile, canonicalShortName, shortener);
|
||||
} else {
|
||||
// not even shortened at all.
|
||||
return potentiallyConflictingFile;
|
||||
}
|
||||
}
|
||||
|
||||
private static File resolveConflict(File conflictingFile, String canonicalShortName, FilenameShortener shortener) {
|
||||
Folder parent = conflictingFile.parent().get();
|
||||
File canonicalFile = parent.file(canonicalShortName);
|
||||
if (canonicalFile.exists()) {
|
||||
// foo (1).lng -> bar.lng
|
||||
String canonicalLongName = shortener.inflate(canonicalShortName);
|
||||
String alternativeLongName;
|
||||
String alternativeShortName;
|
||||
File alternativeFile;
|
||||
String conflictId;
|
||||
do {
|
||||
conflictId = createConflictId();
|
||||
alternativeLongName = canonicalLongName + " (Conflict " + conflictId + ")";
|
||||
alternativeShortName = shortener.deflate(alternativeLongName);
|
||||
alternativeFile = parent.file(alternativeShortName);
|
||||
} while (alternativeFile.exists());
|
||||
LOG.info("Detected conflict {}:\n{}\n{}", conflictId, canonicalFile, conflictingFile);
|
||||
conflictingFile.moveTo(alternativeFile);
|
||||
shortener.saveMapping(alternativeLongName, alternativeShortName);
|
||||
return alternativeFile;
|
||||
} else {
|
||||
// foo (1).lng -> foo.lng
|
||||
conflictingFile.moveTo(canonicalFile);
|
||||
return canonicalFile;
|
||||
}
|
||||
}
|
||||
|
||||
private static String createConflictId() {
|
||||
return UUID.randomUUID().toString().substring(0, UUID_FIRST_GROUP_STRLEN);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,8 +8,7 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.filesystem.shortening;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
@@ -18,9 +17,12 @@ import org.apache.commons.codec.binary.BaseNCodec;
|
||||
import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
import org.cryptomator.io.FileContents;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
class FilenameShortener {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FilenameShortener.class);
|
||||
private static final String LONG_NAME_FILE_EXT = ".lng";
|
||||
private static final ThreadLocal<MessageDigest> SHA1 = new ThreadLocalSha1();
|
||||
private static final BaseNCodec BASE32 = new Base32();
|
||||
@@ -44,7 +46,7 @@ class FilenameShortener {
|
||||
if (longName.length() < threshold) {
|
||||
return longName;
|
||||
} else {
|
||||
final byte[] hashBytes = SHA1.get().digest(longName.getBytes());
|
||||
final byte[] hashBytes = SHA1.get().digest(longName.getBytes(StandardCharsets.UTF_8));
|
||||
final String hash = BASE32.encodeAsString(hashBytes);
|
||||
return hash + LONG_NAME_FILE_EXT;
|
||||
}
|
||||
@@ -70,7 +72,8 @@ class FilenameShortener {
|
||||
private String loadMapping(String shortName) {
|
||||
final File mappingFile = mappingFile(shortName);
|
||||
if (!mappingFile.exists()) {
|
||||
throw new UncheckedIOException(new FileNotFoundException("Mapping file not found " + mappingFile));
|
||||
LOG.warn("Mapping file not found: " + mappingFile);
|
||||
return shortName;
|
||||
} else {
|
||||
return FileContents.UTF_8.readContents(mappingFile);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ class ShorteningFile extends DelegatingFile<ShorteningFolder> {
|
||||
private final FilenameShortener shortener;
|
||||
|
||||
public ShorteningFile(ShorteningFolder parent, File delegate, String name, FilenameShortener shortener) {
|
||||
super(parent, delegate);
|
||||
super(parent, ConflictResolver.resolveConflictIfNecessary(delegate, shortener));
|
||||
this.longName = new AtomicReference<>(name);
|
||||
this.shortener = shortener;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.cryptomator.filesystem.shortening;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
import org.cryptomator.filesystem.inmem.InMemoryFileSystem;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
public class ConflictResolverTest {
|
||||
|
||||
private Folder metadataFolder;
|
||||
private FilenameShortener shortener;
|
||||
private Folder folder;
|
||||
private File canonicalFile;
|
||||
private File conflictingFile;
|
||||
private File resolvedFile;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
metadataFolder = new InMemoryFileSystem();
|
||||
shortener = new FilenameShortener(metadataFolder, 20);
|
||||
folder = Mockito.mock(Folder.class);
|
||||
canonicalFile = Mockito.mock(File.class);
|
||||
conflictingFile = Mockito.mock(File.class);
|
||||
resolvedFile = Mockito.mock(File.class);
|
||||
|
||||
String longName = "hello world, I am a very long file name. certainly longer than twenty characters.exe";
|
||||
String shortName = shortener.deflate(longName);
|
||||
shortener.saveMapping(longName, shortName);
|
||||
String canonicalFileName = shortName;
|
||||
String conflictingFileName = shortName.replace(".lng", " (1).lng");
|
||||
|
||||
Mockito.when(canonicalFile.name()).thenReturn(canonicalFileName);
|
||||
Mockito.when(conflictingFile.name()).thenReturn(conflictingFileName);
|
||||
|
||||
Mockito.when(canonicalFile.exists()).thenReturn(true);
|
||||
Mockito.when(conflictingFile.exists()).thenReturn(true);
|
||||
|
||||
Mockito.doReturn(Optional.of(folder)).when(canonicalFile).parent();
|
||||
Mockito.doReturn(Optional.of(folder)).when(conflictingFile).parent();
|
||||
|
||||
Mockito.when(folder.file(Mockito.anyString())).thenReturn(resolvedFile);
|
||||
Mockito.when(folder.file(canonicalFileName)).thenReturn(canonicalFile);
|
||||
Mockito.when(folder.file(conflictingFileName)).thenReturn(conflictingFile);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoConflictToBeResolved() {
|
||||
File resolved = ConflictResolver.resolveConflictIfNecessary(canonicalFile, new FilenameShortener(metadataFolder, 20));
|
||||
Mockito.verify(conflictingFile, Mockito.never()).moveTo(Mockito.any());
|
||||
Assert.assertSame(canonicalFile, resolved);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConflictToBeResolved() {
|
||||
File resolved = ConflictResolver.resolveConflictIfNecessary(conflictingFile, new FilenameShortener(metadataFolder, 20));
|
||||
Mockito.verify(conflictingFile).moveTo(resolvedFile);
|
||||
Assert.assertSame(resolvedFile, resolved);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,8 +8,6 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.filesystem.shortening;
|
||||
|
||||
import java.io.UncheckedIOException;
|
||||
|
||||
import org.cryptomator.filesystem.FileSystem;
|
||||
import org.cryptomator.filesystem.inmem.InMemoryFileSystem;
|
||||
import org.junit.Assert;
|
||||
@@ -45,12 +43,12 @@ public class FilenameShortenerTest {
|
||||
Assert.assertEquals("short", shortener.inflate("short"));
|
||||
}
|
||||
|
||||
@Test(expected = UncheckedIOException.class)
|
||||
@Test
|
||||
public void testInflateWithoutMappingFile() {
|
||||
FileSystem fs = new InMemoryFileSystem();
|
||||
FilenameShortener shortener = new FilenameShortener(fs, 10);
|
||||
|
||||
shortener.inflate("iJustMadeThisNameUp.lng");
|
||||
Assert.assertEquals("iJustMadeThisNameUp.lng", shortener.inflate("iJustMadeThisNameUp.lng"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
@@ -102,6 +103,31 @@ public class ShorteningFileSystemTest {
|
||||
Assert.assertTrue(correspondingMetadataFile.exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInflate() {
|
||||
final FileSystem underlyingFs = new InMemoryFileSystem();
|
||||
final Folder metadataRoot = underlyingFs.folder(METADATA_DIR_NAME);
|
||||
final FileSystem fs = new ShorteningFileSystem(underlyingFs, METADATA_DIR_NAME, 10);
|
||||
final File correspondingMetadataFile = metadataRoot.folder("QM").folder("JL").file("QMJL5GQUETRX2YRV6XDTJQ6NNM7IEUHP.lng");
|
||||
final Folder shortenedFolder = underlyingFs.folder("QMJL5GQUETRX2YRV6XDTJQ6NNM7IEUHP.lng");
|
||||
shortenedFolder.create();
|
||||
correspondingMetadataFile.parent().get().create();
|
||||
try (WritableFile w = correspondingMetadataFile.openWritable()) {
|
||||
w.write(ByteBuffer.wrap("morethantenchars".getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
Assert.assertTrue(correspondingMetadataFile.exists());
|
||||
Assert.assertTrue(fs.folders().map(Folder::name).anyMatch(n -> n.equals("morethantenchars")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInflateFailedDueToMissingMapping() {
|
||||
final FileSystem underlyingFs = new InMemoryFileSystem();
|
||||
final FileSystem fs = new ShorteningFileSystem(underlyingFs, METADATA_DIR_NAME, 10);
|
||||
final Folder shortenedFolder = underlyingFs.folder("QMJL5GQUETRX2YRV6XDTJQ6NNM7IEUHP.lng");
|
||||
shortenedFolder.create();
|
||||
Assert.assertTrue(fs.folders().map(Folder::name).anyMatch(n -> n.equals("QMJL5GQUETRX2YRV6XDTJQ6NNM7IEUHP.lng")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMoveLongFolders() {
|
||||
final FileSystem underlyingFs = new InMemoryFileSystem();
|
||||
|
||||
22
main/filesystem-nameshortening/src/test/resources/log4j2.xml
Normal file
22
main/filesystem-nameshortening/src/test/resources/log4j2.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?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>
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.1.2</version>
|
||||
</parent>
|
||||
<artifactId>filesystem-nio</artifactId>
|
||||
<name>Cryptomator filesystem: NIO-based physical layer</name>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.1.2</version>
|
||||
</parent>
|
||||
<artifactId>filesystem-stats</artifactId>
|
||||
<name>Cryptomator filesystem: Throughput statistics</name>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.1.2</version>
|
||||
</parent>
|
||||
<artifactId>frontend-api</artifactId>
|
||||
<name>Cryptomator frontend: API</name>
|
||||
|
||||
@@ -14,7 +14,7 @@ import java.util.Optional;
|
||||
public interface Frontend extends AutoCloseable {
|
||||
|
||||
public enum MountParam {
|
||||
MOUNT_NAME, WIN_DRIVE_LETTER
|
||||
MOUNT_NAME, HOSTNAME, WIN_DRIVE_LETTER
|
||||
}
|
||||
|
||||
void mount(Map<MountParam, Optional<String>> map) throws CommandFailedException;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.1.2</version>
|
||||
</parent>
|
||||
<artifactId>frontend-webdav</artifactId>
|
||||
<name>Cryptomator frontend: WebDAV frontend</name>
|
||||
|
||||
@@ -10,10 +10,12 @@ package org.cryptomator.frontend.webdav;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.common.CommonsModule;
|
||||
|
||||
import dagger.Component;
|
||||
|
||||
@Singleton
|
||||
@Component
|
||||
@Component(modules = {CommonsModule.class})
|
||||
public interface WebDavComponent {
|
||||
|
||||
WebDavServer server();
|
||||
|
||||
@@ -32,7 +32,7 @@ public class WindowsCompatibilityServlet extends HttpServlet {
|
||||
protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
resp.addHeader("DAV", "1, 2");
|
||||
resp.addHeader("MS-Author-Via", "DAV");
|
||||
// resp.addHeader("Allow", "OPTIONS, GET, HEAD, POST, TRACE, PROPFIND, PROPPATCH, MKCOL, COPY, PUT, DELETE, MOVE, LOCK, UNLOCK");
|
||||
resp.addHeader("Allow", "OPTIONS, GET, HEAD");
|
||||
resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,11 +32,15 @@ import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
import org.cryptomator.filesystem.ReadableFile;
|
||||
import org.cryptomator.filesystem.jackrabbit.FileLocator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
class DavFile extends DavNode<FileLocator> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DavFile.class);
|
||||
|
||||
public DavFile(FilesystemResourceFactory factory, LockManager lockManager, DavSession session, FileLocator node) {
|
||||
super(factory, lockManager, session, node);
|
||||
}
|
||||
@@ -128,7 +132,7 @@ class DavFile extends DavNode<FileLocator> {
|
||||
@Override
|
||||
public DavProperty<?> getProperty(DavPropertyName name) {
|
||||
if (DavPropertyName.GETCONTENTLENGTH.equals(name)) {
|
||||
return sizeProperty().get();
|
||||
return sizeProperty().orElse(null);
|
||||
} else {
|
||||
return super.getProperty(name);
|
||||
}
|
||||
@@ -147,6 +151,9 @@ class DavFile extends DavNode<FileLocator> {
|
||||
if (node.exists()) {
|
||||
try (ReadableFile src = node.openReadable()) {
|
||||
return Optional.of(new DefaultDavProperty<Long>(DavPropertyName.GETCONTENTLENGTH, src.size()));
|
||||
} catch (RuntimeException e) {
|
||||
LOG.warn("Could not determine file size of " + getResourcePath(), e);
|
||||
return Optional.empty();
|
||||
}
|
||||
} else {
|
||||
return Optional.empty();
|
||||
@@ -167,7 +174,9 @@ class DavFile extends DavNode<FileLocator> {
|
||||
public ActiveLock lock(LockInfo reqLockInfo) throws DavException {
|
||||
ActiveLock lock = super.lock(reqLockInfo);
|
||||
if (!exists()) {
|
||||
getCollection().addMember(this, new NullInputContext());
|
||||
DavFolder parentFolder = getCollection();
|
||||
assert parentFolder != null : "File always has a folder.";
|
||||
parentFolder.addMember(this, new NullInputContext());
|
||||
}
|
||||
return lock;
|
||||
}
|
||||
|
||||
@@ -71,7 +71,9 @@ class DavFileWithRange extends DavFile {
|
||||
try {
|
||||
final Long lower = requestRange.getLeft().isEmpty() ? null : Long.valueOf(requestRange.getLeft());
|
||||
final Long upper = requestRange.getRight().isEmpty() ? null : Long.valueOf(requestRange.getRight());
|
||||
if (lower == null) {
|
||||
if (lower == null && upper == null) {
|
||||
return new ImmutablePair<Long, Long>(0l, contentLength - 1);
|
||||
} else if (lower == null) {
|
||||
return new ImmutablePair<Long, Long>(contentLength - upper, contentLength - 1);
|
||||
} else if (upper == null) {
|
||||
return new ImmutablePair<Long, Long>(lower, contentLength - 1);
|
||||
|
||||
@@ -32,6 +32,7 @@ import org.apache.jackrabbit.webdav.property.DavProperty;
|
||||
import org.apache.jackrabbit.webdav.property.DavPropertyName;
|
||||
import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
|
||||
import org.apache.jackrabbit.webdav.property.ResourceType;
|
||||
import org.cryptomator.common.streams.AutoClosingStream;
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
import org.cryptomator.filesystem.Node;
|
||||
import org.cryptomator.filesystem.WritableFile;
|
||||
@@ -90,7 +91,8 @@ class DavFolder extends DavNode<FolderLocator> {
|
||||
public DavResourceIterator getMembers() {
|
||||
final Stream<DavFolder> folders = node.folders().map(this::folderToDavFolder);
|
||||
final Stream<DavFile> files = node.files().map(this::fileToDavFile);
|
||||
return new DavResourceIteratorImpl(Stream.concat(folders, files).collect(Collectors.toList()));
|
||||
final Stream<DavResource> members = AutoClosingStream.from(Stream.concat(folders, files));
|
||||
return new DavResourceIteratorImpl(members.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private DavFolder folderToDavFolder(FolderLocator memberFolder) {
|
||||
@@ -115,9 +117,15 @@ class DavFolder extends DavNode<FolderLocator> {
|
||||
* Error 404 if no child with the given name exists
|
||||
*/
|
||||
private Node getMemberNode(String name) throws DavException {
|
||||
return node.children().filter(c -> c.name().equals(name)).findAny().orElseThrow(() -> {
|
||||
return new DavException(DavServletResponse.SC_NOT_FOUND, "No such file or directory: " + node.getResourcePath() + name);
|
||||
});
|
||||
Node file = node.file(name);
|
||||
Node folder = node.folder(name);
|
||||
if (file.exists()) {
|
||||
return file;
|
||||
} else if (folder.exists()) {
|
||||
return folder;
|
||||
} else {
|
||||
throw new DavException(DavServletResponse.SC_NOT_FOUND, "No such file or directory: " + node.getResourcePath() + name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.apache.jackrabbit.webdav.property.DavPropertySet;
|
||||
import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
|
||||
import org.apache.jackrabbit.webdav.property.PropEntry;
|
||||
import org.cryptomator.filesystem.jackrabbit.FileSystemResourceLocator;
|
||||
import org.cryptomator.filesystem.jackrabbit.FolderLocator;
|
||||
|
||||
abstract class DavNode<T extends FileSystemResourceLocator> implements DavResource {
|
||||
|
||||
@@ -187,18 +188,14 @@ abstract class DavNode<T extends FileSystemResourceLocator> implements DavResour
|
||||
}
|
||||
|
||||
@Override
|
||||
public DavResource getCollection() {
|
||||
public DavFolder getCollection() {
|
||||
if (node.isRootLocation()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
assert node.parent().isPresent() : "as my mom always sais: if it's not root, it has a parent";
|
||||
final FileSystemResourceLocator parentPath = node.parent().get();
|
||||
try {
|
||||
return factory.createResource(parentPath, session);
|
||||
} catch (DavException e) {
|
||||
throw new IllegalStateException("Unable to get parent resource with path " + parentPath, e);
|
||||
}
|
||||
final FolderLocator parentPath = node.parent().get();
|
||||
return factory.createFolder(parentPath, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -158,7 +158,7 @@ class ExclusiveSharedLockManager implements LockManager {
|
||||
}
|
||||
// or otherwise look for parent locks:
|
||||
if (locator.parent().isPresent()) {
|
||||
return getLockInternal(type, scope, locator.parent().get(), depth++);
|
||||
return getLockInternal(type, scope, locator.parent().get(), depth + 1);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ class FilesystemResourceFactory implements DavResourceFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DavResource createResource(DavResourceLocator locator, DavSession session) throws DavException {
|
||||
public DavResource createResource(DavResourceLocator locator, DavSession session) {
|
||||
if (locator instanceof FolderLocator) {
|
||||
FolderLocator folder = (FolderLocator) locator;
|
||||
return createFolder(folder, session);
|
||||
|
||||
@@ -12,11 +12,13 @@ package org.cryptomator.frontend.webdav.mount;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
@@ -26,15 +28,18 @@ import org.cryptomator.frontend.CommandFailedException;
|
||||
import org.cryptomator.frontend.Frontend.MountParam;
|
||||
|
||||
@Singleton
|
||||
final class MacOsXWebDavMounter implements WebDavMounterStrategy {
|
||||
final class MacOsXAppleScriptWebDavMounter implements WebDavMounterStrategy {
|
||||
|
||||
private final Comparator<String> semVerComparator;
|
||||
|
||||
@Inject
|
||||
MacOsXWebDavMounter() {
|
||||
MacOsXAppleScriptWebDavMounter(@Named("SemVer") Comparator<String> semVerComparator) {
|
||||
this.semVerComparator = semVerComparator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldWork() {
|
||||
return SystemUtils.IS_OS_MAC_OSX;
|
||||
return SystemUtils.IS_OS_MAC_OSX && semVerComparator.compare(SystemUtils.OS_VERSION, "10.10") >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -0,0 +1,89 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014, 2016 Sebastian Stenzel, Markus Kreusch
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation, strategy fine tuning
|
||||
* Markus Kreusch - Refactored WebDavMounter to use strategy pattern
|
||||
******************************************************************************/
|
||||
package org.cryptomator.frontend.webdav.mount;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.frontend.CommandFailedException;
|
||||
import org.cryptomator.frontend.Frontend.MountParam;
|
||||
import org.cryptomator.frontend.webdav.mount.command.Script;
|
||||
|
||||
@Singleton
|
||||
final class MacOsXShellScriptWebDavMounter implements WebDavMounterStrategy {
|
||||
|
||||
private final Comparator<String> semVerComparator;
|
||||
|
||||
@Inject
|
||||
MacOsXShellScriptWebDavMounter(@Named("SemVer") Comparator<String> semVerComparator) {
|
||||
this.semVerComparator = semVerComparator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldWork() {
|
||||
return SystemUtils.IS_OS_MAC_OSX && semVerComparator.compare(SystemUtils.OS_VERSION, "10.10") < 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warmUp(int serverPort) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebDavMount mount(URI uri, Map<MountParam, Optional<String>> mountParams) throws CommandFailedException {
|
||||
final String mountName = mountParams.getOrDefault(MountParam.MOUNT_NAME, Optional.empty()).orElseThrow(() -> {
|
||||
return new IllegalArgumentException("Missing mount parameter MOUNT_NAME.");
|
||||
});
|
||||
|
||||
// we don't use the uri to derive a path, as it *could* be longer than 255 chars.
|
||||
final String path = "/Volumes/Cryptomator_" + UUID.randomUUID().toString();
|
||||
final Script mountScript = Script.fromLines("mkdir \"$MOUNT_PATH\"", "mount_webdav -S -v $MOUNT_NAME \"$DAV_AUTHORITY$DAV_PATH\" \"$MOUNT_PATH\"").addEnv("DAV_AUTHORITY", uri.getRawAuthority())
|
||||
.addEnv("DAV_PATH", uri.getRawPath()).addEnv("MOUNT_PATH", path).addEnv("MOUNT_NAME", mountName);
|
||||
mountScript.execute();
|
||||
return new MacWebDavMount(path);
|
||||
}
|
||||
|
||||
private static class MacWebDavMount extends AbstractWebDavMount {
|
||||
private final String mountPath;
|
||||
private final Script revealScript;
|
||||
private final Script unmountScript;
|
||||
|
||||
private MacWebDavMount(String mountPath) {
|
||||
this.mountPath = mountPath;
|
||||
this.revealScript = Script.fromLines("open \"$MOUNT_PATH\"").addEnv("MOUNT_PATH", mountPath);
|
||||
this.unmountScript = Script.fromLines("diskutil umount $MOUNT_PATH").addEnv("MOUNT_PATH", mountPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unmount() throws CommandFailedException {
|
||||
// only attempt unmount if user didn't unmount manually:
|
||||
if (Files.exists(FileSystems.getDefault().getPath(mountPath))) {
|
||||
unmountScript.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reveal() throws CommandFailedException {
|
||||
revealScript.execute();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,74 +19,87 @@ import javax.inject.Singleton;
|
||||
|
||||
@Singleton
|
||||
class MountStrategies implements Collection<WebDavMounterStrategy> {
|
||||
|
||||
|
||||
private final Collection<WebDavMounterStrategy> delegate;
|
||||
|
||||
|
||||
@Inject
|
||||
MountStrategies(LinuxGvfsWebDavMounter linuxMounter, MacOsXWebDavMounter osxMounter, WindowsWebDavMounter winMounter) {
|
||||
delegate = unmodifiableList(asList(linuxMounter, osxMounter, winMounter));
|
||||
MountStrategies(LinuxGvfsWebDavMounter linuxMounter, MacOsXAppleScriptWebDavMounter osxAppleScriptMounter, MacOsXShellScriptWebDavMounter osxShellScriptMounter, WindowsWebDavMounter winMounter) {
|
||||
delegate = unmodifiableList(asList(linuxMounter, osxAppleScriptMounter, osxShellScriptMounter, winMounter));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return delegate.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return delegate.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return delegate.contains(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<WebDavMounterStrategy> iterator() {
|
||||
return delegate.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return delegate.toArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T[] toArray(T[] a) {
|
||||
return delegate.toArray(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(WebDavMounterStrategy e) {
|
||||
return delegate.add(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
return delegate.remove(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> c) {
|
||||
return delegate.containsAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends WebDavMounterStrategy> c) {
|
||||
return delegate.addAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
return delegate.removeAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
return delegate.retainAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
delegate.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return delegate.equals(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return delegate.hashCode();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -11,9 +11,16 @@ package org.cryptomator.frontend.webdav.mount;
|
||||
|
||||
import static org.cryptomator.frontend.webdav.mount.command.Script.fromLines;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -21,24 +28,31 @@ import java.util.regex.Pattern;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.CharUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.frontend.CommandFailedException;
|
||||
import org.cryptomator.frontend.Frontend.MountParam;
|
||||
import org.cryptomator.frontend.webdav.mount.command.CommandResult;
|
||||
import org.cryptomator.frontend.webdav.mount.command.Script;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A {@link WebDavMounterStrategy} utilizing the "net use" command.
|
||||
* <p>
|
||||
* Tested on Windows 7 but should also work on Windows 8.
|
||||
* Tested on Windows 7, 8.1 and 10.
|
||||
*/
|
||||
@Singleton
|
||||
final class WindowsWebDavMounter implements WebDavMounterStrategy {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WindowsWebDavMounter.class);
|
||||
private static final Pattern WIN_MOUNT_DRIVELETTER_PATTERN = Pattern.compile("\\s*([A-Z]):\\s*");
|
||||
private static final int MAX_MOUNT_ATTEMPTS = 8;
|
||||
private static final char AUTO_ASSIGN_DRIVE_LETTER = '*';
|
||||
private static final Pattern REG_QUERY_PROXY_OVERRIDES_PATTERN = Pattern.compile("\\s*ProxyOverride\\s+REG_SZ\\s+(.*)\\s*");
|
||||
private static final String AUTO_ASSIGN_DRIVE_LETTER = "*";
|
||||
private static final String LOCALHOST = "localhost";
|
||||
private static final int MOUNT_TIMEOUT_SECONDS = 60;
|
||||
private final WindowsDriveLetters driveLetters;
|
||||
|
||||
@Inject
|
||||
@@ -58,56 +72,79 @@ final class WindowsWebDavMounter implements WebDavMounterStrategy {
|
||||
|
||||
@Override
|
||||
public WebDavMount mount(URI uri, Map<MountParam, Optional<String>> mountParams) throws CommandFailedException {
|
||||
final Character driveLetter = mountParams.get(MountParam.WIN_DRIVE_LETTER).map(CharUtils::toCharacterObject).orElse(AUTO_ASSIGN_DRIVE_LETTER);
|
||||
if (driveLetters.getOccupiedDriveLetters().contains(driveLetter)) {
|
||||
final String driveLetter = mountParams.getOrDefault(MountParam.WIN_DRIVE_LETTER, Optional.empty()).orElse(AUTO_ASSIGN_DRIVE_LETTER);
|
||||
if (driveLetters.getOccupiedDriveLetters().contains(CharUtils.toChar(driveLetter))) {
|
||||
throw new CommandFailedException("Drive letter occupied.");
|
||||
}
|
||||
|
||||
final String driveLetterStr = driveLetter.charValue() == AUTO_ASSIGN_DRIVE_LETTER ? CharUtils.toString(AUTO_ASSIGN_DRIVE_LETTER) : driveLetter + ":";
|
||||
final Script localhostMountScript = fromLines("net use %DRIVE_LETTER% \\\\localhost@%DAV_PORT%\\DavWWWRoot%DAV_UNC_PATH% /persistent:no");
|
||||
localhostMountScript.addEnv("DRIVE_LETTER", driveLetterStr);
|
||||
localhostMountScript.addEnv("DAV_PORT", String.valueOf(uri.getPort()));
|
||||
localhostMountScript.addEnv("DAV_UNC_PATH", uri.getRawPath().replace('/', '\\'));
|
||||
CommandResult mountResult;
|
||||
final String hostname = mountParams.getOrDefault(MountParam.HOSTNAME, Optional.empty()).orElse(LOCALHOST);
|
||||
try {
|
||||
mountResult = localhostMountScript.execute(5, TimeUnit.SECONDS);
|
||||
} catch (CommandFailedException ex) {
|
||||
final Script ipv6literaltMountScript = fromLines("net use %DRIVE_LETTER% \\\\0--1.ipv6-literal.net@%DAV_PORT%\\DavWWWRoot%DAV_UNC_PATH% /persistent:no");
|
||||
ipv6literaltMountScript.addEnv("DRIVE_LETTER", driveLetterStr);
|
||||
ipv6literaltMountScript.addEnv("DAV_PORT", String.valueOf(uri.getPort()));
|
||||
ipv6literaltMountScript.addEnv("DAV_UNC_PATH", uri.getRawPath().replace('/', '\\'));
|
||||
final Script proxyBypassScript = fromLines(
|
||||
"reg add \"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\" /v \"ProxyOverride\" /d \"<local>;0--1.ipv6-literal.net;0--1.ipv6-literal.net:%DAV_PORT%\" /f");
|
||||
proxyBypassScript.addEnv("DAV_PORT", String.valueOf(uri.getPort()));
|
||||
mountResult = bypassProxyAndRetryMount(localhostMountScript, ipv6literaltMountScript, proxyBypassScript);
|
||||
final URI adjustedUri = new URI(uri.getScheme(), uri.getUserInfo(), hostname, uri.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment());
|
||||
CommandResult mountResult = mount(adjustedUri, driveLetter);
|
||||
return new WindowsWebDavMount(AUTO_ASSIGN_DRIVE_LETTER.equals(driveLetter) ? getDriveLetter(mountResult.getStdOut()) : driveLetter);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalArgumentException("Invalid host: " + hostname);
|
||||
}
|
||||
return new WindowsWebDavMount(driveLetter.charValue() == AUTO_ASSIGN_DRIVE_LETTER ? getDriveLetter(mountResult.getStdOut()) : driveLetter);
|
||||
}
|
||||
|
||||
private CommandResult bypassProxyAndRetryMount(Script localhostMountScript, Script ipv6literalMountScript, Script proxyBypassScript) throws CommandFailedException {
|
||||
CommandFailedException latestException = null;
|
||||
for (int i = 0; i < MAX_MOUNT_ATTEMPTS; i++) {
|
||||
try {
|
||||
// wait a moment before next attempt
|
||||
Thread.sleep(5000);
|
||||
proxyBypassScript.execute();
|
||||
// alternate localhost and 0--1.ipv6literal.net
|
||||
final Script mountScript = (i % 2 == 0) ? localhostMountScript : ipv6literalMountScript;
|
||||
return mountScript.execute(3, TimeUnit.SECONDS);
|
||||
} catch (CommandFailedException ex) {
|
||||
latestException = ex;
|
||||
} catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new CommandFailedException(ex);
|
||||
private CommandResult mount(URI uri, String driveLetter) throws CommandFailedException {
|
||||
try {
|
||||
addProxyOverrides(uri);
|
||||
} catch (IOException e) {
|
||||
throw new CommandFailedException(e);
|
||||
}
|
||||
|
||||
final String driveLetterStr = AUTO_ASSIGN_DRIVE_LETTER.equals(driveLetter) ? AUTO_ASSIGN_DRIVE_LETTER : driveLetter + ":";
|
||||
final Script mountScript = fromLines("net use %DRIVE_LETTER% \\\\%DAV_HOST%@%DAV_PORT%\\DavWWWRoot%DAV_UNC_PATH% /persistent:no");
|
||||
mountScript.addEnv("DRIVE_LETTER", driveLetterStr);
|
||||
mountScript.addEnv("DAV_HOST", uri.getHost());
|
||||
mountScript.addEnv("DAV_PORT", String.valueOf(uri.getPort()));
|
||||
mountScript.addEnv("DAV_UNC_PATH", uri.getRawPath().replace('/', '\\'));
|
||||
return mountScript.execute(MOUNT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void addProxyOverrides(URI uri) throws IOException, CommandFailedException {
|
||||
try {
|
||||
// get existing value for ProxyOverride key from reqistry:
|
||||
ProcessBuilder query = new ProcessBuilder("reg", "query", "\"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\"", "/v", "ProxyOverride");
|
||||
Process queryCmd = query.start();
|
||||
String queryStdOut = IOUtils.toString(queryCmd.getInputStream(), StandardCharsets.UTF_8);
|
||||
int queryResult = queryCmd.waitFor();
|
||||
|
||||
// determine new value for ProxyOverride key:
|
||||
Set<String> overrides = new HashSet<>();
|
||||
Matcher matcher = REG_QUERY_PROXY_OVERRIDES_PATTERN.matcher(queryStdOut);
|
||||
if (queryResult == 0 && matcher.find()) {
|
||||
String[] existingOverrides = StringUtils.split(matcher.group(1), ';');
|
||||
overrides.addAll(Arrays.asList(existingOverrides));
|
||||
}
|
||||
overrides.removeIf(s -> s.startsWith(uri.getHost() + ":"));
|
||||
overrides.add("<local>");
|
||||
overrides.add(uri.getHost());
|
||||
overrides.add(uri.getHost() + ":" + uri.getPort());
|
||||
|
||||
// set new value:
|
||||
String overridesStr = StringUtils.join(overrides, ';');
|
||||
ProcessBuilder add = new ProcessBuilder("reg", "add", "\"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\"", "/v", "ProxyOverride", "/d", "\"" + overridesStr + "\"", "/f");
|
||||
LOG.debug("Invoking command: " + StringUtils.join(add.command(), ' '));
|
||||
Process addCmd = add.start();
|
||||
int addResult = addCmd.waitFor();
|
||||
if (addResult != 0) {
|
||||
String addStdErr = IOUtils.toString(addCmd.getErrorStream(), StandardCharsets.UTF_8);
|
||||
throw new CommandFailedException(addStdErr);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
InterruptedIOException ioException = new InterruptedIOException();
|
||||
ioException.initCause(e);
|
||||
throw ioException;
|
||||
}
|
||||
throw latestException;
|
||||
}
|
||||
|
||||
private Character getDriveLetter(String result) throws CommandFailedException {
|
||||
private String getDriveLetter(String result) throws CommandFailedException {
|
||||
final Matcher matcher = WIN_MOUNT_DRIVELETTER_PATTERN.matcher(result);
|
||||
if (matcher.find()) {
|
||||
return CharUtils.toCharacterObject(matcher.group(1));
|
||||
return matcher.group(1);
|
||||
} else {
|
||||
throw new CommandFailedException("Failed to get a drive letter from net use output.");
|
||||
}
|
||||
@@ -118,10 +155,10 @@ final class WindowsWebDavMounter implements WebDavMounterStrategy {
|
||||
private final Script openExplorerScript;
|
||||
private final Script unmountScript;
|
||||
|
||||
private WindowsWebDavMount(Character driveLetter) {
|
||||
this.driveLetter = driveLetter;
|
||||
private WindowsWebDavMount(String driveLetter) {
|
||||
this.driveLetter = CharUtils.toCharacterObject(driveLetter);
|
||||
this.openExplorerScript = fromLines("start explorer.exe " + driveLetter + ":");
|
||||
this.unmountScript = fromLines("net use " + driveLetter + ": /delete").addEnv("DRIVE_LETTER", Character.toString(driveLetter));
|
||||
this.unmountScript = fromLines("net use " + driveLetter + ": /delete");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -30,6 +30,7 @@ public final class CommandResult {
|
||||
|
||||
/**
|
||||
* Constructs a CommandResult from a terminated process and closes all its streams.
|
||||
*
|
||||
* @param process An <strong>already finished</strong> process.
|
||||
*/
|
||||
CommandResult(Process process) {
|
||||
@@ -52,7 +53,7 @@ public final class CommandResult {
|
||||
logDebugInfo();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Data written to STDOUT
|
||||
*/
|
||||
@@ -94,10 +95,10 @@ public final class CommandResult {
|
||||
assertNoException();
|
||||
int exitValue = getExitValue();
|
||||
if (exitValue != 0) {
|
||||
throw new CommandFailedException(format("Command execution failed. Exit code: %d\n" + "# Output:\n" + "%s\n" + "# Error:\n" + "%s", exitValue, stdout, stderr));
|
||||
throw new CommandFailedException(format("Command execution failed. Exit code: %d %n# Output:%n%s %n# Error: %n%s", exitValue, stdout, stderr));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void assertNoException() throws CommandFailedException {
|
||||
if (exception != null) {
|
||||
throw exception;
|
||||
|
||||
@@ -20,15 +20,15 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
import org.cryptomator.frontend.CommandFailedException;
|
||||
|
||||
final class FutureCommandResult implements Future<CommandResult>, Runnable {
|
||||
|
||||
|
||||
private final Process process;
|
||||
private final AtomicBoolean canceled = new AtomicBoolean();
|
||||
private final AtomicBoolean done = new AtomicBoolean();
|
||||
private final Lock lock = new ReentrantLock();
|
||||
private final Condition doneCondition = lock.newCondition();
|
||||
|
||||
|
||||
private CommandFailedException exception;
|
||||
|
||||
|
||||
FutureCommandResult(Process process) {
|
||||
this.process = process;
|
||||
}
|
||||
@@ -49,7 +49,7 @@ final class FutureCommandResult implements Future<CommandResult>, Runnable {
|
||||
public boolean isCancelled() {
|
||||
return canceled.get();
|
||||
}
|
||||
|
||||
|
||||
private void setDone() {
|
||||
lock.lock();
|
||||
try {
|
||||
@@ -69,7 +69,7 @@ final class FutureCommandResult implements Future<CommandResult>, Runnable {
|
||||
public CommandResult get() throws InterruptedException, ExecutionException {
|
||||
lock.lock();
|
||||
try {
|
||||
while(!done.get()) {
|
||||
while (!done.get()) {
|
||||
doneCondition.await();
|
||||
}
|
||||
} finally {
|
||||
@@ -85,8 +85,12 @@ final class FutureCommandResult implements Future<CommandResult>, Runnable {
|
||||
public CommandResult get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
|
||||
lock.lock();
|
||||
try {
|
||||
while(!done.get()) {
|
||||
doneCondition.await(timeout, unit);
|
||||
boolean doneInTime = true;
|
||||
if (!done.get()) {
|
||||
doneInTime = doneCondition.await(timeout, unit);
|
||||
}
|
||||
if (!doneInTime) {
|
||||
throw new TimeoutException();
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
|
||||
1
main/jacoco-report/.gitignore
vendored
Normal file
1
main/jacoco-report/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target/
|
||||
86
main/jacoco-report/pom.xml
Normal file
86
main/jacoco-report/pom.xml
Normal file
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright (c) 2016 Sebastian Stenzel This file is licensed under the terms of the MIT license. See the LICENSE.txt file for more info. -->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.1.2</version>
|
||||
</parent>
|
||||
<artifactId>jacoco-report</artifactId>
|
||||
<name>Cryptomator Code Coverage Report</name>
|
||||
|
||||
<dependencies>
|
||||
<!-- Commons -->
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>commons</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>commons-test</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Filesystem Layers -->
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>filesystem-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>filesystem-charsets</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>filesystem-crypto</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>filesystem-crypto-integration-tests</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>filesystem-inmemory</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>filesystem-nameshortening</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>filesystem-nio</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>filesystem-stats</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Frontends -->
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>frontend-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>frontend-webdav</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>report-aggregate</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>report-aggregate</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
53
main/pom.xml
53
main/pom.xml
@@ -7,7 +7,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.1.2</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Cryptomator</name>
|
||||
|
||||
@@ -35,21 +35,14 @@
|
||||
<hamcrest.version>1.3</hamcrest.version> <!-- keep in sync with version required by JUnit -->
|
||||
<commons-io.version>2.4</commons-io.version>
|
||||
<commons-collections.version>4.0</commons-collections.version>
|
||||
<commons-lang3.version>3.3.2</commons-lang3.version>
|
||||
<commons-lang3.version>3.4</commons-lang3.version>
|
||||
<commons-codec.version>1.10</commons-codec.version>
|
||||
<commons-httpclient.version>3.1</commons-httpclient.version>
|
||||
<jackson-databind.version>2.4.4</jackson-databind.version>
|
||||
<mockito.version>1.10.19</mockito.version>
|
||||
<dagger.version>2.0.2</dagger.version>
|
||||
<dagger.version>2.4</dagger.version>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<!-- modules -->
|
||||
@@ -70,6 +63,11 @@
|
||||
<artifactId>filesystem-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>filesystem-charsets</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>filesystem-nio</artifactId>
|
||||
@@ -81,6 +79,12 @@
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>filesystem-invariants-tests</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>filesystem-nameshortening</artifactId>
|
||||
@@ -254,14 +258,6 @@
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-jul</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<modules>
|
||||
@@ -278,13 +274,21 @@
|
||||
<module>frontend-api</module>
|
||||
<module>frontend-webdav</module>
|
||||
<module>ui</module>
|
||||
<module>filesystem-charsets</module>
|
||||
</modules>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>uber-jar</id>
|
||||
<id>release</id>
|
||||
<modules>
|
||||
<module>uber-jar</module>
|
||||
<module>ant-kit</module>
|
||||
</modules>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>test-coverage</id>
|
||||
<modules>
|
||||
<module>jacoco-report</module>
|
||||
</modules>
|
||||
</profile>
|
||||
</profiles>
|
||||
@@ -311,7 +315,7 @@
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>0.7.5.201505241946</version>
|
||||
<version>0.7.7.201606060606</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>prepare-agent</id>
|
||||
@@ -320,6 +324,12 @@
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>**/*_*</exclude>
|
||||
<exclude>**/Dagger*</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
@@ -338,6 +348,9 @@
|
||||
<artifactId>coveralls-maven-plugin</artifactId>
|
||||
<version>4.0.0</version>
|
||||
<configuration>
|
||||
<jacocoReports>
|
||||
<jacocoReport>jacoco-report/target/site/jacoco-aggregate/jacoco.xml</jacocoReport>
|
||||
</jacocoReports>
|
||||
<repoToken>${env.COVERALLS_REPO_TOKEN}</repoToken>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.1.2</version>
|
||||
</parent>
|
||||
<artifactId>uber-jar</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.1.2</version>
|
||||
</parent>
|
||||
<artifactId>ui</artifactId>
|
||||
<name>Cryptomator GUI</name>
|
||||
@@ -38,6 +38,10 @@
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>filesystem-crypto</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>filesystem-charsets</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>filesystem-stats</artifactId>
|
||||
@@ -99,5 +103,12 @@
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>commons-test</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Zxcvbn -->
|
||||
<dependency>
|
||||
<groupId>com.nulab-inc</groupId>
|
||||
<artifactId>zxcvbn</artifactId>
|
||||
<version>1.1.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -36,6 +36,8 @@ public class Cryptomator {
|
||||
private static final CleanShutdownPerformer CLEAN_SHUTDOWN_PERFORMER = new CleanShutdownPerformer();
|
||||
|
||||
public static void main(String[] args) {
|
||||
String cryptomatorVersion = Optional.ofNullable(Cryptomator.class.getPackage().getImplementationVersion()).orElse("SNAPSHOT");
|
||||
LOG.info("Starting Cryptomator {} on {} {} ({})", cryptomatorVersion, SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
|
||||
if (SystemUtils.IS_OS_MAC_OSX) {
|
||||
/*
|
||||
* On OSX we're in an awkward position. We need to register a handler in the main thread of this application. However, we can't
|
||||
|
||||
@@ -29,5 +29,5 @@ interface CryptomatorComponent {
|
||||
|
||||
Localization localization();
|
||||
|
||||
TrayIconUtil trayIconUtil();
|
||||
ExitUtil exitUtil();
|
||||
}
|
||||
@@ -8,13 +8,13 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.common.CommonsModule;
|
||||
import org.cryptomator.crypto.engine.impl.CryptoEngineModule;
|
||||
import org.cryptomator.frontend.FrontendFactory;
|
||||
import org.cryptomator.frontend.webdav.WebDavServer;
|
||||
@@ -24,7 +24,6 @@ import org.cryptomator.ui.model.VaultObjectMapperProvider;
|
||||
import org.cryptomator.ui.settings.Settings;
|
||||
import org.cryptomator.ui.settings.SettingsProvider;
|
||||
import org.cryptomator.ui.util.DeferredCloser;
|
||||
import org.cryptomator.ui.util.SemVerComparator;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
@@ -33,7 +32,7 @@ import dagger.Provides;
|
||||
import javafx.application.Application;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
@Module(includes = CryptoEngineModule.class)
|
||||
@Module(includes = {CryptoEngineModule.class, CommonsModule.class})
|
||||
class CryptomatorModule {
|
||||
|
||||
private final Application application;
|
||||
@@ -65,13 +64,6 @@ class CryptomatorModule {
|
||||
return closer;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("SemVer")
|
||||
Comparator<String> provideSemVerComparator() {
|
||||
return new SemVerComparator();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("VaultJsonMapper")
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user