Compare commits
91 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
6deb30307e | ||
|
|
7357829741 | ||
|
|
4bd04150c1 | ||
|
|
cf35772c18 | ||
|
|
b0fd226c4c | ||
|
|
0d188d1c0c | ||
|
|
c6016ec7b2 | ||
|
|
e8719a1f9b | ||
|
|
27baf78029 | ||
|
|
bf5ce9a3a5 | ||
|
|
bcfe040784 | ||
|
|
d9b88ad1b7 | ||
|
|
e66e5b1d96 | ||
|
|
588166dce9 |
6
.gitignore
vendored
@@ -11,3 +11,9 @@
|
||||
.classpath
|
||||
target/
|
||||
test-output/
|
||||
|
||||
# IntelliJ Settings Files #
|
||||
.idea/
|
||||
out/
|
||||
.idea_modules/
|
||||
*.iws
|
||||
|
||||
@@ -39,7 +39,7 @@ addons:
|
||||
name: "cryptomator/cryptomator"
|
||||
notification_email: sebastian.stenzel@cryptomator.org
|
||||
build_command: "mvn -fmain/pom.xml clean test -DskipTests"
|
||||
branch_pattern: coverity_scan
|
||||
branch_pattern: release.*
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
|
||||
@@ -45,7 +45,7 @@ For more information on the security details visit [cryptomator.org](https://cry
|
||||
|
||||
* 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
|
||||
|
||||
|
||||
@@ -14,6 +14,12 @@
|
||||
</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>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.1.1</version>
|
||||
</parent>
|
||||
<artifactId>ant-kit</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
@@ -60,6 +60,16 @@
|
||||
<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>
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
<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" />
|
||||
|
||||
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
@@ -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
|
||||
@@ -10,17 +10,26 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.1.1</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>
|
||||
@@ -29,11 +38,6 @@
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>commons</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.1.1</version>
|
||||
</parent>
|
||||
<artifactId>commons</artifactId>
|
||||
<name>Cryptomator common</name>
|
||||
@@ -44,6 +44,11 @@
|
||||
<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>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.1.1</version>
|
||||
</parent>
|
||||
<artifactId>filesystem-api</artifactId>
|
||||
<name>Cryptomator filesystem: API</name>
|
||||
|
||||
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.1</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
@@ -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>1.0.4</version>
|
||||
<version>1.1.1</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>1.0.4</version>
|
||||
<version>1.1.1</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;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
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;
|
||||
@@ -18,6 +19,7 @@ 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;
|
||||
@@ -81,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +110,7 @@ class CryptorImpl implements Cryptor {
|
||||
assert keyFile != null;
|
||||
|
||||
// check version
|
||||
if (!CURRENT_VAULT_VERSION.equals(keyFile.getVersion())) {
|
||||
if (!SUPPORTED_VAULT_VERSIONS.contains(keyFile.getVersion())) {
|
||||
throw new UnsupportedVaultFormatException(keyFile.getVersion(), CURRENT_VAULT_VERSION);
|
||||
}
|
||||
|
||||
@@ -116,12 +119,12 @@ class CryptorImpl implements Cryptor {
|
||||
final SecretKey kek = new SecretKeySpec(kekBytes, ENCRYPTION_ALG);
|
||||
this.macKey = AesKeyWrap.unwrap(kek, keyFile.getMacMasterKey(), MAC_ALG);
|
||||
// future use (as soon as we need to prevent downgrade attacks):
|
||||
// final Mac mac = new ThreadLocalMac(macKey, MAC_ALG).get();
|
||||
// final byte[] versionMac = mac.doFinal(ByteBuffer.allocate(Integer.BYTES).putInt(CURRENT_VAULT_VERSION).array());
|
||||
// if (!MessageDigest.isEqual(versionMac, keyFile.getVersionMac())) {
|
||||
// destroyQuietly(macKey);
|
||||
// throw new UnsupportedVaultFormatException(Integer.MAX_VALUE, CURRENT_VAULT_VERSION);
|
||||
// }
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package org.cryptomator.filesystem.crypto;
|
||||
|
||||
import static org.cryptomator.filesystem.crypto.Constants.DIR_PREFIX;
|
||||
|
||||
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.io.FileContents;
|
||||
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 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 (canonicalFile.exists()) {
|
||||
// there must not be two directories pointing to the same directory id. In this case no human interaction is needed to resolve this conflict:
|
||||
if (isDirectory && FileContents.UTF_8.readContents(canonicalFile).equals(FileContents.UTF_8.readContents(conflictingFile))) {
|
||||
conflictingFile.delete();
|
||||
return canonicalFile;
|
||||
}
|
||||
|
||||
// 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 {
|
||||
conflictingFile.moveTo(canonicalFile);
|
||||
return canonicalFile;
|
||||
}
|
||||
} else {
|
||||
// not decryptable; false positive
|
||||
return conflictingFile;
|
||||
}
|
||||
}
|
||||
|
||||
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,9 +25,7 @@ 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
|
||||
|
||||
@@ -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,37 +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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,24 +81,60 @@ class CryptoFolder extends CryptoNode implements Folder {
|
||||
}));
|
||||
}
|
||||
|
||||
/* ======================= children ======================= */
|
||||
|
||||
@Override
|
||||
public Stream<? extends Node> children() {
|
||||
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() {
|
||||
final Stream<? extends File> files = physicalFolder().filter(Folder::exists).map(Folder::files).orElse(Stream.empty());
|
||||
return 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
|
||||
@@ -98,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() {
|
||||
final Stream<? extends File> files = physicalFolder().filter(Folder::exists).map(Folder::files).orElse(Stream.empty());
|
||||
return 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();
|
||||
@@ -176,7 +206,7 @@ class CryptoFolder extends CryptoNode implements Folder {
|
||||
// cut all ties:
|
||||
this.invalidateDirectoryIdsRecursively();
|
||||
|
||||
assert!exists();
|
||||
assert !exists();
|
||||
assert target.exists();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -21,20 +21,20 @@ 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");
|
||||
}
|
||||
@@ -44,7 +44,7 @@ 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");
|
||||
}
|
||||
@@ -62,23 +62,24 @@ public class CryptorImplTest {
|
||||
@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,159 @@
|
||||
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 resolved = conflictResolver.resolveIfNecessary(canonicalFile);
|
||||
Assert.assertSame(canonicalFile, resolved);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnrelatedName() {
|
||||
File resolved = conflictResolver.resolveIfNecessary(unrelatedFile);
|
||||
Assert.assertSame(unrelatedFile, resolved);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConflictingFile() {
|
||||
File resolved = conflictResolver.resolveIfNecessary(conflictingFile);
|
||||
Mockito.verify(conflictingFile).moveTo(resolved);
|
||||
Assert.assertSame(resolved, resolved);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConflictingFileIfCanonicalDoesntExist() {
|
||||
Mockito.when(canonicalFile.exists()).thenReturn(false);
|
||||
File resolved = conflictResolver.resolveIfNecessary(conflictingFile);
|
||||
Mockito.verify(conflictingFile).moveTo(canonicalFile);
|
||||
Assert.assertSame(canonicalFile, resolved);
|
||||
}
|
||||
|
||||
@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);
|
||||
Mockito.when(canonicalFolder.openReadable()).thenReturn(directoryId1);
|
||||
Mockito.when(conflictingFolder.openReadable()).thenReturn(directoryId1);
|
||||
Mockito.when(directoryId1.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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.1.1</version>
|
||||
</parent>
|
||||
<artifactId>filesystem-inmemory</artifactId>
|
||||
<name>Cryptomator filesystem: In-memory mock</name>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.1.1</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;
|
||||
@@ -35,8 +37,10 @@ class FileSystemFactories implements Iterable<FileSystemFactory> {
|
||||
add("ShorteningFileSystem > InMemoryFileSystem", this::createShorteningFileSystemInMemory);
|
||||
add("StatsFileSystem > NioFileSystem", this::createStatsFileSystemNio);
|
||||
add("StatsFileSystem > InMemoryFileSystem", this::createStatsFileSystemInMemory);
|
||||
add("StatsFileSystem > CryptoFileSystem > ShorteningFileSystem > InMemoryFileSystem", this::createCompoundFileSystemInMemory);
|
||||
add("StatsFileSystem > CryptoFileSystem > ShorteningFileSystem > NioFileSystem", this::createCompoundFileSystemNio);
|
||||
add("NormalizingFileSystem > NioFileSystem", this::createNormalizingFileSystemNio);
|
||||
add("NormalizingFileSystem > InMemoryFileSystem", this::createNormalizingFileSystemInMemory);
|
||||
add("StatsFileSystem > NormalizingFileSystem > CryptoFileSystem > ShorteningFileSystem > InMemoryFileSystem", this::createCompoundFileSystemInMemory);
|
||||
add("StatsFileSystem > NormalizingFileSystem > CryptoFileSystem > ShorteningFileSystem > NioFileSystem", this::createCompoundFileSystemNio);
|
||||
}
|
||||
|
||||
private FileSystem createCryptoFileSystemInMemory() {
|
||||
@@ -63,6 +67,14 @@ class FileSystemFactories implements Iterable<FileSystemFactory> {
|
||||
return createStatsFileSystem(createInMemoryFileSystem());
|
||||
}
|
||||
|
||||
private FileSystem createNormalizingFileSystemNio() {
|
||||
return createNormalizingFileSystem(createInMemoryFileSystem());
|
||||
}
|
||||
|
||||
private FileSystem createNormalizingFileSystemInMemory() {
|
||||
return createNormalizingFileSystem(createInMemoryFileSystem());
|
||||
}
|
||||
|
||||
private FileSystem createCompoundFileSystemNio() {
|
||||
return createCompoundFileSystem(createNioFileSystem());
|
||||
}
|
||||
@@ -84,13 +96,17 @@ class FileSystemFactories implements Iterable<FileSystemFactory> {
|
||||
}
|
||||
|
||||
private FileSystem createCompoundFileSystem(FileSystem delegate) {
|
||||
return createStatsFileSystem(createCryptoFileSystem(createShorteningFileSystem(delegate)));
|
||||
return createStatsFileSystem(createNormalizingFileSystem(createCryptoFileSystem(createShorteningFileSystem(delegate))));
|
||||
}
|
||||
|
||||
private FileSystem createStatsFileSystem(FileSystem delegate) {
|
||||
return new StatsFileSystem(delegate);
|
||||
}
|
||||
|
||||
private FileSystem createNormalizingFileSystem(FileSystem delegate) {
|
||||
return new NormalizedNameFileSystem(delegate, Form.NFC);
|
||||
}
|
||||
|
||||
private FileSystem createCryptoFileSystem(FileSystem delegate) {
|
||||
CRYPTO_FS_COMP.cryptoFileSystemFactory().initializeNew(delegate, "aPassphrase");
|
||||
return CRYPTO_FS_COMP.cryptoFileSystemFactory().unlockExisting(delegate, "aPassphrase", Mockito.mock(CryptoFileSystemDelegate.class));
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.1.1</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,6 @@
|
||||
*******************************************************************************/
|
||||
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;
|
||||
@@ -19,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();
|
||||
@@ -71,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
@@ -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>1.0.4</version>
|
||||
<version>1.1.1</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>1.0.4</version>
|
||||
<version>1.1.1</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>1.0.4</version>
|
||||
<version>1.1.1</version>
|
||||
</parent>
|
||||
<artifactId>frontend-api</artifactId>
|
||||
<name>Cryptomator frontend: API</name>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.1.1</version>
|
||||
</parent>
|
||||
<artifactId>frontend-webdav</artifactId>
|
||||
<name>Cryptomator frontend: WebDAV frontend</name>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.1.0-SNAPSHOT</version>
|
||||
<version>1.1.1</version>
|
||||
</parent>
|
||||
<artifactId>jacoco-report</artifactId>
|
||||
<name>Cryptomator Code Coverage Report</name>
|
||||
@@ -26,6 +26,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>
|
||||
@@ -79,4 +83,4 @@
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
</project>
|
||||
|
||||
35
main/pom.xml
@@ -7,7 +7,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.1.1</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Cryptomator</name>
|
||||
|
||||
@@ -43,23 +43,6 @@
|
||||
<dagger.version>2.4</dagger.version>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>jacoco-snapshots</id>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<!-- modules -->
|
||||
@@ -80,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>
|
||||
@@ -270,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>
|
||||
@@ -294,6 +274,7 @@
|
||||
<module>frontend-api</module>
|
||||
<module>frontend-webdav</module>
|
||||
<module>ui</module>
|
||||
<module>filesystem-charsets</module>
|
||||
</modules>
|
||||
|
||||
<profiles>
|
||||
@@ -334,7 +315,7 @@
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>0.7.7-SNAPSHOT</version>
|
||||
<version>0.7.7.201606060606</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>prepare-agent</id>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.1.1</version>
|
||||
</parent>
|
||||
<artifactId>uber-jar</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.1.1</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>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
* Jean-Noël Charon - implementation of github issue #56
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui;
|
||||
|
||||
@@ -17,6 +18,8 @@ import java.awt.Toolkit;
|
||||
import java.awt.TrayIcon;
|
||||
import java.awt.TrayIcon.MessageType;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -75,6 +78,11 @@ class ExitUtil {
|
||||
private void initTrayIconExitHandler(Runnable exitCommand) {
|
||||
final TrayIcon trayIcon = createTrayIcon(exitCommand);
|
||||
try {
|
||||
// double clicking tray icon should open Cryptomator
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
trayIcon.addMouseListener(new TrayIconMouseListener());
|
||||
}
|
||||
|
||||
SystemTray.getSystemTray().add(trayIcon);
|
||||
mainWindow.setOnCloseRequest((e) -> {
|
||||
if (Platform.isImplicitExit()) {
|
||||
@@ -168,6 +176,17 @@ class ExitUtil {
|
||||
});
|
||||
}
|
||||
|
||||
private class TrayIconMouseListener extends MouseAdapter {
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
|
||||
restoreFromTray(new ActionEvent(e.getSource(), e.getID(), e.paramString()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void restoreFromTray(ActionEvent event) {
|
||||
Platform.runLater(() -> {
|
||||
mainWindow.show();
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
* Jean-Noël Charon - password strength meter
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.controllers;
|
||||
|
||||
@@ -21,18 +22,24 @@ import org.cryptomator.crypto.engine.UnsupportedVaultFormatException;
|
||||
import org.cryptomator.ui.controls.SecPasswordField;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
import org.cryptomator.ui.util.PasswordStrengthUtil;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.text.Text;
|
||||
|
||||
@Singleton
|
||||
@@ -41,13 +48,16 @@ public class ChangePasswordController extends LocalizedFXMLViewController {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ChangePasswordController.class);
|
||||
|
||||
private final Application app;
|
||||
private final PasswordStrengthUtil strengthRater;
|
||||
final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
|
||||
private Optional<ChangePasswordListener> listener = Optional.empty();
|
||||
private final IntegerProperty passwordStrength = new SimpleIntegerProperty(); // 0-4
|
||||
|
||||
@Inject
|
||||
public ChangePasswordController(Application app, Localization localization) {
|
||||
public ChangePasswordController(Application app, PasswordStrengthUtil strengthRater, Localization localization) {
|
||||
super(localization);
|
||||
this.app = app;
|
||||
this.strengthRater = strengthRater;
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -68,12 +78,39 @@ public class ChangePasswordController extends LocalizedFXMLViewController {
|
||||
@FXML
|
||||
private Hyperlink downloadsPageLink;
|
||||
|
||||
@FXML
|
||||
private Label passwordStrengthLabel;
|
||||
|
||||
@FXML
|
||||
private Region passwordStrengthLevel0;
|
||||
|
||||
@FXML
|
||||
private Region passwordStrengthLevel1;
|
||||
|
||||
@FXML
|
||||
private Region passwordStrengthLevel2;
|
||||
|
||||
@FXML
|
||||
private Region passwordStrengthLevel3;
|
||||
|
||||
@FXML
|
||||
private Region passwordStrengthLevel4;
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
BooleanBinding oldPasswordIsEmpty = oldPasswordField.textProperty().isEmpty();
|
||||
BooleanBinding newPasswordIsEmpty = newPasswordField.textProperty().isEmpty();
|
||||
BooleanBinding passwordsDiffer = newPasswordField.textProperty().isNotEqualTo(retypePasswordField.textProperty());
|
||||
EasyBind.subscribe(vault, this::vaultDidChange);
|
||||
changePasswordButton.disableProperty().bind(oldPasswordIsEmpty.or(newPasswordIsEmpty.or(passwordsDiffer)));
|
||||
passwordStrength.bind(EasyBind.map(newPasswordField.textProperty(), strengthRater::computeRate));
|
||||
|
||||
passwordStrengthLevel0.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(0), strengthRater::getBackgroundWithStrengthColor));
|
||||
passwordStrengthLevel1.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(1), strengthRater::getBackgroundWithStrengthColor));
|
||||
passwordStrengthLevel2.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(2), strengthRater::getBackgroundWithStrengthColor));
|
||||
passwordStrengthLevel3.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(3), strengthRater::getBackgroundWithStrengthColor));
|
||||
passwordStrengthLevel4.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(4), strengthRater::getBackgroundWithStrengthColor));
|
||||
passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -81,6 +118,15 @@ public class ChangePasswordController extends LocalizedFXMLViewController {
|
||||
return getClass().getResource("/fxml/change_password.fxml");
|
||||
}
|
||||
|
||||
private void vaultDidChange(Vault newVault) {
|
||||
oldPasswordField.clear();
|
||||
newPasswordField.clear();
|
||||
retypePasswordField.clear();
|
||||
// trigger "default" change to refresh key bindings:
|
||||
changePasswordButton.setDefaultButton(false);
|
||||
changePasswordButton.setDefaultButton(true);
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// Downloads link
|
||||
// ****************************************
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
* Copyright (c) 2014, 2016 Sebastian Stenzel
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
* Jean-Noël Charon - password strength meter
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui.controllers;
|
||||
|
||||
@@ -20,29 +21,37 @@ import javax.inject.Singleton;
|
||||
import org.cryptomator.ui.controls.SecPasswordField;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
import org.cryptomator.ui.util.PasswordStrengthUtil;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
@Singleton
|
||||
public class InitializeController extends LocalizedFXMLViewController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(InitializeController.class);
|
||||
|
||||
private final PasswordStrengthUtil strengthRater;
|
||||
final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
|
||||
private Optional<InitializationListener> listener = Optional.empty();
|
||||
private final IntegerProperty passwordStrength = new SimpleIntegerProperty(); // 0-4
|
||||
|
||||
@Inject
|
||||
public InitializeController(Localization localization) {
|
||||
public InitializeController(Localization localization, PasswordStrengthUtil strengthRater) {
|
||||
super(localization);
|
||||
this.strengthRater = strengthRater;
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -57,11 +66,38 @@ public class InitializeController extends LocalizedFXMLViewController {
|
||||
@FXML
|
||||
private Label messageLabel;
|
||||
|
||||
@FXML
|
||||
private Label passwordStrengthLabel;
|
||||
|
||||
@FXML
|
||||
private Region passwordStrengthLevel0;
|
||||
|
||||
@FXML
|
||||
private Region passwordStrengthLevel1;
|
||||
|
||||
@FXML
|
||||
private Region passwordStrengthLevel2;
|
||||
|
||||
@FXML
|
||||
private Region passwordStrengthLevel3;
|
||||
|
||||
@FXML
|
||||
private Region passwordStrengthLevel4;
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
BooleanBinding passwordIsEmpty = passwordField.textProperty().isEmpty();
|
||||
BooleanBinding passwordsDiffer = passwordField.textProperty().isNotEqualTo(retypePasswordField.textProperty());
|
||||
EasyBind.subscribe(vault, this::vaultDidChange);
|
||||
okButton.disableProperty().bind(passwordIsEmpty.or(passwordsDiffer));
|
||||
passwordStrength.bind(EasyBind.map(passwordField.textProperty(), strengthRater::computeRate));
|
||||
|
||||
passwordStrengthLevel0.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(0), strengthRater::getBackgroundWithStrengthColor));
|
||||
passwordStrengthLevel1.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(1), strengthRater::getBackgroundWithStrengthColor));
|
||||
passwordStrengthLevel2.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(2), strengthRater::getBackgroundWithStrengthColor));
|
||||
passwordStrengthLevel3.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(3), strengthRater::getBackgroundWithStrengthColor));
|
||||
passwordStrengthLevel4.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(4), strengthRater::getBackgroundWithStrengthColor));
|
||||
passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -69,6 +105,14 @@ public class InitializeController extends LocalizedFXMLViewController {
|
||||
return getClass().getResource("/fxml/initialize.fxml");
|
||||
}
|
||||
|
||||
private void vaultDidChange(Vault newVault) {
|
||||
passwordField.clear();
|
||||
retypePasswordField.clear();
|
||||
// trigger "default" change to refresh key bindings:
|
||||
okButton.setDefaultButton(false);
|
||||
okButton.setDefaultButton(true);
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// OK button
|
||||
// ****************************************
|
||||
|
||||
@@ -102,7 +102,7 @@ public class MacWarningsController extends LocalizedFXMLViewController {
|
||||
|
||||
@FXML
|
||||
private void didClickMoreInformationButton(ActionEvent event) {
|
||||
application.getHostServices().showDocument("https://cryptomator.org/faq/#macWarning");
|
||||
application.getHostServices().showDocument("https://cryptomator.freshdesk.com/support/solutions/articles/16000003666-what-does-mac-authentication-failed-mean-");
|
||||
}
|
||||
|
||||
private void unauthenticatedResourcesDidChange(Change<? extends String> change) {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
* Jean-Noël Charon - confirmation dialog on vault removal
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui.controllers;
|
||||
|
||||
@@ -16,19 +17,22 @@ import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.ui.controls.DirectoryListCell;
|
||||
import org.cryptomator.ui.model.UpgradeStrategies;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.cryptomator.ui.model.VaultFactory;
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
import org.cryptomator.ui.settings.Settings;
|
||||
import org.cryptomator.ui.util.DialogBuilderUtil;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
import org.fxmisc.easybind.monadic.MonadicBinding;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -37,6 +41,7 @@ import javafx.application.Platform;
|
||||
import javafx.beans.binding.Binding;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.binding.BooleanExpression;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
@@ -46,10 +51,13 @@ import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Pane;
|
||||
@@ -71,18 +79,20 @@ public class MainController extends LocalizedFXMLViewController {
|
||||
private final Provider<UnlockedController> unlockedControllerProvider;
|
||||
private final Lazy<ChangePasswordController> changePasswordController;
|
||||
private final Lazy<SettingsController> settingsController;
|
||||
private final Lazy<UpgradeStrategies> upgradeStrategies;
|
||||
private final ObjectProperty<AbstractFXMLViewController> activeController = new SimpleObjectProperty<>();
|
||||
private final ObservableList<Vault> vaults;
|
||||
private final ObjectProperty<Vault> selectedVault = new SimpleObjectProperty<>();
|
||||
private final MonadicBinding<Boolean> isSelectedVaultUnlocked = EasyBind.select(selectedVault).selectObject(Vault::unlockedProperty);;
|
||||
private final Binding<Boolean> canEditSelectedVault = EasyBind.combine(selectedVault.isNull(), isSelectedVaultUnlocked.orElse(false), Boolean::logicalOr);
|
||||
private final BooleanExpression isSelectedVaultUnlocked = BooleanBinding.booleanExpression(EasyBind.select(selectedVault).selectObject(Vault::unlockedProperty).orElse(false));
|
||||
private final BooleanExpression isSelectedVaultValid = BooleanBinding.booleanExpression(EasyBind.monadic(selectedVault).map(Vault::isValidVaultDirectory).orElse(false));
|
||||
private final BooleanExpression canEditSelectedVault = selectedVault.isNotNull().and(isSelectedVaultUnlocked.not());
|
||||
private final BooleanBinding isShowingSettings;
|
||||
private final Map<Vault, UnlockedController> unlockedVaults = new HashMap<>();
|
||||
|
||||
@Inject
|
||||
public MainController(@Named("mainWindow") Stage mainWindow, Localization localization, Settings settings, VaultFactory vaultFactoy, Lazy<WelcomeController> welcomeController,
|
||||
Lazy<InitializeController> initializeController, Lazy<NotFoundController> notFoundController, Lazy<UpgradeController> upgradeController, Lazy<UnlockController> unlockController,
|
||||
Provider<UnlockedController> unlockedControllerProvider, Lazy<ChangePasswordController> changePasswordController, Lazy<SettingsController> settingsController) {
|
||||
Provider<UnlockedController> unlockedControllerProvider, Lazy<ChangePasswordController> changePasswordController, Lazy<SettingsController> settingsController, Lazy<UpgradeStrategies> upgradeStrategies) {
|
||||
super(localization);
|
||||
this.mainWindow = mainWindow;
|
||||
this.vaultFactoy = vaultFactoy;
|
||||
@@ -94,6 +104,7 @@ public class MainController extends LocalizedFXMLViewController {
|
||||
this.unlockedControllerProvider = unlockedControllerProvider;
|
||||
this.changePasswordController = changePasswordController;
|
||||
this.settingsController = settingsController;
|
||||
this.upgradeStrategies = upgradeStrategies;
|
||||
this.vaults = FXCollections.observableList(settings.getDirectories());
|
||||
this.vaults.addListener((Change<? extends Vault> c) -> {
|
||||
settings.save();
|
||||
@@ -106,6 +117,9 @@ public class MainController extends LocalizedFXMLViewController {
|
||||
@FXML
|
||||
private ContextMenu vaultListCellContextMenu;
|
||||
|
||||
@FXML
|
||||
private MenuItem changePasswordMenuItem;
|
||||
|
||||
@FXML
|
||||
private ContextMenu addVaultContextMenu;
|
||||
|
||||
@@ -136,8 +150,9 @@ public class MainController extends LocalizedFXMLViewController {
|
||||
vaultList.setCellFactory(this::createDirecoryListCell);
|
||||
activeController.set(welcomeController.get());
|
||||
selectedVault.bind(vaultList.getSelectionModel().selectedItemProperty());
|
||||
removeVaultButton.disableProperty().bind(canEditSelectedVault);
|
||||
removeVaultButton.disableProperty().bind(canEditSelectedVault.not());
|
||||
emptyListInstructions.visibleProperty().bind(Bindings.isEmpty(vaults));
|
||||
changePasswordMenuItem.visibleProperty().bind(isSelectedVaultValid);
|
||||
|
||||
EasyBind.subscribe(selectedVault, this::selectedVaultDidChange);
|
||||
EasyBind.subscribe(activeController, this::activeControllerDidChange);
|
||||
@@ -190,7 +205,7 @@ public class MainController extends LocalizedFXMLViewController {
|
||||
@FXML
|
||||
private void didClickAddExistingVaults(ActionEvent event) {
|
||||
final FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Cryptomator vault", "*" + Vault.VAULT_FILE_EXTENSION));
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Cryptomator Masterkey", "*" + Vault.VAULT_FILE_EXTENSION));
|
||||
final List<File> files = fileChooser.showOpenMultipleDialog(mainWindow);
|
||||
if (files != null) {
|
||||
for (final File file : files) {
|
||||
@@ -228,9 +243,18 @@ public class MainController extends LocalizedFXMLViewController {
|
||||
|
||||
@FXML
|
||||
private void didClickRemoveSelectedEntry(ActionEvent e) {
|
||||
vaults.remove(selectedVault.get());
|
||||
if (vaults.isEmpty()) {
|
||||
activeController.set(welcomeController.get());
|
||||
Alert confirmDialog = DialogBuilderUtil.buildConfirmationDialog( //
|
||||
localization.getString("main.directoryList.remove.confirmation.title"), //
|
||||
localization.getString("main.directoryList.remove.confirmation.header"), //
|
||||
localization.getString("main.directoryList.remove.confirmation.content"), //
|
||||
SystemUtils.IS_OS_MAC_OSX ? ButtonType.CANCEL : ButtonType.OK);
|
||||
|
||||
Optional<ButtonType> choice = confirmDialog.showAndWait();
|
||||
if (ButtonType.OK.equals(choice.get())) {
|
||||
vaults.remove(selectedVault.get());
|
||||
if (vaults.isEmpty()) {
|
||||
activeController.set(welcomeController.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,7 +291,7 @@ public class MainController extends LocalizedFXMLViewController {
|
||||
this.showUnlockedView(newValue);
|
||||
} else if (!newValue.doesVaultDirectoryExist()) {
|
||||
this.showNotFoundView();
|
||||
} else if (newValue.isValidVaultDirectory() && newValue.needsUpgrade()) {
|
||||
} else if (newValue.isValidVaultDirectory() && upgradeStrategies.get().getUpgradeStrategy(newValue).isPresent()) {
|
||||
this.showUpgradeView();
|
||||
} else if (newValue.isValidVaultDirectory()) {
|
||||
this.showUnlockView();
|
||||
|
||||
@@ -43,6 +43,9 @@ public class SettingsController extends LocalizedFXMLViewController {
|
||||
@FXML
|
||||
private TextField portField;
|
||||
|
||||
@FXML
|
||||
private Label useIpv6Label;
|
||||
|
||||
@FXML
|
||||
private CheckBox useIpv6Checkbox;
|
||||
|
||||
@@ -55,7 +58,8 @@ public class SettingsController extends LocalizedFXMLViewController {
|
||||
checkForUpdatesCheckbox.setSelected(settings.isCheckForUpdatesEnabled() && !areUpdatesManagedExternally());
|
||||
portField.setText(String.valueOf(settings.getPort()));
|
||||
portField.addEventFilter(KeyEvent.KEY_TYPED, this::filterNumericKeyEvents);
|
||||
useIpv6Checkbox.setDisable(!SystemUtils.IS_OS_WINDOWS);
|
||||
useIpv6Label.setVisible(SystemUtils.IS_OS_WINDOWS);
|
||||
useIpv6Checkbox.setVisible(SystemUtils.IS_OS_WINDOWS);
|
||||
useIpv6Checkbox.setSelected(SystemUtils.IS_OS_WINDOWS && settings.shouldUseIpv6());
|
||||
versionLabel.setText(String.format(localization.getString("settings.version.label"), applicationVersion().orElse("SNAPSHOT")));
|
||||
|
||||
@@ -81,7 +85,7 @@ public class SettingsController extends LocalizedFXMLViewController {
|
||||
private void portDidChange(String newValue) {
|
||||
try {
|
||||
int port = Integer.parseInt(newValue);
|
||||
if (port < Settings.MIN_PORT || port > Settings.MAX_PORT) {
|
||||
if (!settings.isPortValid(port)) {
|
||||
settings.setPort(Settings.DEFAULT_PORT);
|
||||
} else {
|
||||
settings.setPort(port);
|
||||
|
||||
@@ -119,7 +119,7 @@ public class UnlockController extends LocalizedFXMLViewController {
|
||||
}
|
||||
unlockButton.disableProperty().bind(passwordField.textProperty().isEmpty());
|
||||
|
||||
EasyBind.subscribe(vault, this::vaultChanged);
|
||||
EasyBind.subscribe(vault, this::vaultDidChange);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -127,7 +127,7 @@ public class UnlockController extends LocalizedFXMLViewController {
|
||||
return getClass().getResource("/fxml/unlock.fxml");
|
||||
}
|
||||
|
||||
private void vaultChanged(Vault newVault) {
|
||||
private void vaultDidChange(Vault newVault) {
|
||||
if (newVault == null) {
|
||||
return;
|
||||
}
|
||||
@@ -149,6 +149,9 @@ public class UnlockController extends LocalizedFXMLViewController {
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
chooseSelectedDriveLetter();
|
||||
}
|
||||
// trigger "default" change to refresh key bindings:
|
||||
unlockButton.setDefaultButton(false);
|
||||
unlockButton.setDefaultButton(true);
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
|
||||
@@ -7,8 +7,10 @@ import java.util.concurrent.ExecutorService;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.cryptomator.ui.model.UpgradeInstruction;
|
||||
import org.cryptomator.ui.model.UpgradeInstruction.UpgradeFailedException;
|
||||
import org.cryptomator.ui.controls.SecPasswordField;
|
||||
import org.cryptomator.ui.model.UpgradeStrategies;
|
||||
import org.cryptomator.ui.model.UpgradeStrategy;
|
||||
import org.cryptomator.ui.model.UpgradeStrategy.UpgradeFailedException;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
@@ -16,7 +18,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Binding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
@@ -30,19 +31,24 @@ public class UpgradeController extends LocalizedFXMLViewController {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UpgradeController.class);
|
||||
|
||||
final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<Optional<UpgradeStrategy>> strategy = new SimpleObjectProperty<>();
|
||||
private final UpgradeStrategies strategies;
|
||||
private final ExecutorService exec;
|
||||
private final Binding<Optional<UpgradeInstruction>> upgradeInstruction = EasyBind.monadic(vault).map(Vault::availableUpgrade);
|
||||
private Optional<UpgradeListener> listener = Optional.empty();
|
||||
|
||||
@Inject
|
||||
public UpgradeController(Localization localization, ExecutorService exec) {
|
||||
public UpgradeController(Localization localization, UpgradeStrategies strategies, ExecutorService exec) {
|
||||
super(localization);
|
||||
this.strategies = strategies;
|
||||
this.exec = exec;
|
||||
}
|
||||
|
||||
@FXML
|
||||
private Label upgradeLabel;
|
||||
|
||||
@FXML
|
||||
private SecPasswordField passwordField;
|
||||
|
||||
@FXML
|
||||
private Button upgradeButton;
|
||||
|
||||
@@ -54,11 +60,13 @@ public class UpgradeController extends LocalizedFXMLViewController {
|
||||
|
||||
@Override
|
||||
protected void initialize() {
|
||||
upgradeLabel.textProperty().bind(EasyBind.monadic(upgradeInstruction).map(instruction -> {
|
||||
upgradeLabel.textProperty().bind(EasyBind.monadic(strategy).map(instruction -> {
|
||||
return instruction.map(this::upgradeNotification).orElse("");
|
||||
}).orElse(""));
|
||||
|
||||
EasyBind.subscribe(vault, this::vaultChanged);
|
||||
upgradeButton.disableProperty().bind(passwordField.textProperty().isEmpty().or(passwordField.disabledProperty()));
|
||||
|
||||
EasyBind.subscribe(vault, this::vaultDidChange);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,16 +74,20 @@ public class UpgradeController extends LocalizedFXMLViewController {
|
||||
return getClass().getResource("/fxml/upgrade.fxml");
|
||||
}
|
||||
|
||||
private void vaultChanged(Vault newVault) {
|
||||
private void vaultDidChange(Vault newVault) {
|
||||
errorLabel.setText(null);
|
||||
strategy.set(strategies.getUpgradeStrategy(newVault));
|
||||
// trigger "default" change to refresh key bindings:
|
||||
upgradeButton.setDefaultButton(false);
|
||||
upgradeButton.setDefaultButton(true);
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// Upgrade label
|
||||
// ****************************************
|
||||
|
||||
private String upgradeNotification(UpgradeInstruction instruction) {
|
||||
return instruction.getNotification(vault.get(), localization);
|
||||
private String upgradeNotification(UpgradeStrategy instruction) {
|
||||
return instruction.getNotification(vault.get());
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
@@ -84,36 +96,45 @@ public class UpgradeController extends LocalizedFXMLViewController {
|
||||
|
||||
@FXML
|
||||
private void didClickUpgradeButton(ActionEvent event) {
|
||||
upgradeInstruction.getValue().ifPresent(this::upgrade);
|
||||
strategy.getValue().ifPresent(this::upgrade);
|
||||
}
|
||||
|
||||
private void upgrade(UpgradeInstruction instruction) {
|
||||
Vault v = vault.getValue();
|
||||
Objects.requireNonNull(v);
|
||||
private void upgrade(UpgradeStrategy instruction) {
|
||||
Vault v = Objects.requireNonNull(vault.getValue());
|
||||
passwordField.setDisable(true);
|
||||
progressIndicator.setVisible(true);
|
||||
upgradeButton.setDisable(true);
|
||||
exec.submit(() -> {
|
||||
if (!instruction.isApplicable(v)) {
|
||||
LOG.error("No upgrade needed for " + v.path().getValue());
|
||||
throw new IllegalStateException("No ugprade needed for " + v.path().getValue());
|
||||
}
|
||||
try {
|
||||
instruction.upgrade(v, localization);
|
||||
Platform.runLater(() -> {
|
||||
progressIndicator.setVisible(false);
|
||||
upgradeButton.setDisable(false);
|
||||
listener.ifPresent(UpgradeListener::didUpgrade);
|
||||
});
|
||||
instruction.upgrade(v, passwordField.getCharacters());
|
||||
Platform.runLater(this::showNextUpgrade);
|
||||
} catch (UpgradeFailedException e) {
|
||||
Platform.runLater(() -> {
|
||||
errorLabel.setText(e.getLocalizedMessage());
|
||||
});
|
||||
} finally {
|
||||
Platform.runLater(() -> {
|
||||
progressIndicator.setVisible(false);
|
||||
upgradeButton.setDisable(false);
|
||||
passwordField.setDisable(false);
|
||||
passwordField.swipe();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void showNextUpgrade() {
|
||||
errorLabel.setText(null);
|
||||
Optional<UpgradeStrategy> nextStrategy = strategies.getUpgradeStrategy(vault.getValue());
|
||||
if (nextStrategy.isPresent()) {
|
||||
strategy.set(nextStrategy);
|
||||
} else {
|
||||
listener.ifPresent(UpgradeListener::didUpgrade);
|
||||
}
|
||||
}
|
||||
|
||||
/* callback */
|
||||
|
||||
public void setListener(UpgradeListener listener) {
|
||||
|
||||
@@ -154,6 +154,7 @@ public class WelcomeController extends LocalizedFXMLViewController {
|
||||
Platform.runLater(() -> {
|
||||
this.updateLink.setText(msg);
|
||||
this.updateLink.setVisible(true);
|
||||
this.updateLink.setDisable(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
|
||||
public interface UpgradeInstruction {
|
||||
|
||||
static UpgradeInstruction[] AVAILABLE_INSTRUCTIONS = {new UpgradeVersion3DropBundleExtension()};
|
||||
|
||||
/**
|
||||
* @return Localized string to display to the user when an upgrade is needed.
|
||||
*/
|
||||
String getNotification(Vault vault, Localization localization);
|
||||
|
||||
/**
|
||||
* Upgrades a vault. Might take a moment, should be run in a background thread.
|
||||
*/
|
||||
void upgrade(Vault vault, Localization localization) throws UpgradeFailedException;
|
||||
|
||||
/**
|
||||
* Determines in O(1), if an upgrade can be applied to a vault.
|
||||
*
|
||||
* @return <code>true</code> if and only if the vault can be migrated to a newer version without the risk of data losses.
|
||||
*/
|
||||
boolean isApplicable(Vault vault);
|
||||
|
||||
/**
|
||||
* Thrown when data migration failed.
|
||||
*/
|
||||
public class UpgradeFailedException extends Exception {
|
||||
|
||||
UpgradeFailedException() {
|
||||
}
|
||||
|
||||
UpgradeFailedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Singleton
|
||||
public class UpgradeStrategies {
|
||||
|
||||
private final Collection<UpgradeStrategy> strategies;
|
||||
|
||||
@Inject
|
||||
public UpgradeStrategies(UpgradeVersion3DropBundleExtension upgrader1, UpgradeVersion3to4 upgrader2) {
|
||||
strategies = Collections.unmodifiableList(Arrays.asList(upgrader1, upgrader2));
|
||||
}
|
||||
|
||||
public Optional<UpgradeStrategy> getUpgradeStrategy(Vault vault) {
|
||||
return strategies.stream().filter(strategy -> {
|
||||
return strategy.isApplicable(vault);
|
||||
}).findFirst();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
|
||||
import javax.inject.Provider;
|
||||
|
||||
import org.cryptomator.crypto.engine.Cryptor;
|
||||
import org.cryptomator.crypto.engine.InvalidPassphraseException;
|
||||
import org.cryptomator.crypto.engine.UnsupportedVaultFormatException;
|
||||
import org.cryptomator.filesystem.crypto.Constants;
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public abstract class UpgradeStrategy {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UpgradeStrategy.class);
|
||||
|
||||
protected final Provider<Cryptor> cryptorProvider;
|
||||
protected final Localization localization;
|
||||
|
||||
UpgradeStrategy(Provider<Cryptor> cryptorProvider, Localization localization) {
|
||||
this.cryptorProvider = cryptorProvider;
|
||||
this.localization = localization;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Localized string to display to the user when an upgrade is needed.
|
||||
*/
|
||||
public abstract String getNotification(Vault vault);
|
||||
|
||||
/**
|
||||
* Upgrades a vault. Might take a moment, should be run in a background thread.
|
||||
*/
|
||||
public void upgrade(Vault vault, CharSequence passphrase) throws UpgradeFailedException {
|
||||
final Cryptor cryptor = cryptorProvider.get();
|
||||
try {
|
||||
final Path masterkeyFile = vault.path().getValue().resolve(Constants.MASTERKEY_FILENAME);
|
||||
final byte[] masterkeyFileContents = Files.readAllBytes(masterkeyFile);
|
||||
cryptor.readKeysFromMasterkeyFile(masterkeyFileContents, passphrase);
|
||||
// create backup, as soon as we know the password was correct:
|
||||
final Path masterkeyBackupFile = vault.path().getValue().resolve(Constants.MASTERKEY_BACKUP_FILENAME);
|
||||
Files.copy(masterkeyFile, masterkeyBackupFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
// do stuff:
|
||||
upgrade(vault, cryptor);
|
||||
// write updated masterkey file:
|
||||
final byte[] upgradedMasterkeyFileContents = cryptor.writeKeysToMasterkeyFile(passphrase);
|
||||
final Path masterkeyFileAfterUpgrading = vault.path().getValue().resolve(Constants.MASTERKEY_FILENAME); // path may have changed
|
||||
Files.write(masterkeyFileAfterUpgrading, upgradedMasterkeyFileContents, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
} catch (InvalidPassphraseException e) {
|
||||
throw new UpgradeFailedException(localization.getString("unlock.errorMessage.wrongPassword"));
|
||||
} catch (IOException | UnsupportedVaultFormatException e) {
|
||||
LOG.warn("Upgrade failed.", e);
|
||||
throw new UpgradeFailedException("Upgrade failed. Details in log message.");
|
||||
} finally {
|
||||
cryptor.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void upgrade(Vault vault, Cryptor cryptor) throws UpgradeFailedException;
|
||||
|
||||
/**
|
||||
* Determines in O(1), if an upgrade can be applied to a vault.
|
||||
*
|
||||
* @return <code>true</code> if and only if the vault can be migrated to a newer version without the risk of data losses.
|
||||
*/
|
||||
public abstract boolean isApplicable(Vault vault);
|
||||
|
||||
/**
|
||||
* Thrown when data migration failed.
|
||||
*/
|
||||
public static class UpgradeFailedException extends Exception {
|
||||
|
||||
UpgradeFailedException() {
|
||||
}
|
||||
|
||||
UpgradeFailedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,22 +1,40 @@
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.cryptomator.crypto.engine.Cryptor;
|
||||
import org.cryptomator.crypto.engine.InvalidPassphraseException;
|
||||
import org.cryptomator.crypto.engine.UnsupportedVaultFormatException;
|
||||
import org.cryptomator.filesystem.crypto.Constants;
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
import org.cryptomator.ui.settings.Settings;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javafx.application.Platform;
|
||||
|
||||
class UpgradeVersion3DropBundleExtension implements UpgradeInstruction {
|
||||
@Singleton
|
||||
class UpgradeVersion3DropBundleExtension extends UpgradeStrategy {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion3DropBundleExtension.class);
|
||||
private final Settings settings;
|
||||
|
||||
@Inject
|
||||
public UpgradeVersion3DropBundleExtension(Provider<Cryptor> cryptorProvider, Localization localization, Settings settings) {
|
||||
super(cryptorProvider, localization);
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNotification(Vault vault, Localization localization) {
|
||||
public String getNotification(Vault vault) {
|
||||
String fmt = localization.getString("upgrade.version3dropBundleExtension.msg");
|
||||
Path path = vault.path().getValue();
|
||||
String oldVaultName = path.getFileName().toString();
|
||||
@@ -25,7 +43,26 @@ class UpgradeVersion3DropBundleExtension implements UpgradeInstruction {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void upgrade(Vault vault, Localization localization) throws UpgradeFailedException {
|
||||
public void upgrade(Vault vault, CharSequence passphrase) throws UpgradeFailedException {
|
||||
final Cryptor cryptor = cryptorProvider.get();
|
||||
try {
|
||||
final Path masterkeyFile = vault.path().getValue().resolve(Constants.MASTERKEY_FILENAME);
|
||||
final byte[] masterkeyFileContents = Files.readAllBytes(masterkeyFile);
|
||||
cryptor.readKeysFromMasterkeyFile(masterkeyFileContents, passphrase);
|
||||
upgrade(vault, cryptor);
|
||||
// don't write new masterkey. this is a special case, as we were stupid and didn't increase the vault version with this upgrade...
|
||||
} catch (InvalidPassphraseException e) {
|
||||
throw new UpgradeFailedException(localization.getString("unlock.errorMessage.wrongPassword"));
|
||||
} catch (IOException | UnsupportedVaultFormatException e) {
|
||||
LOG.warn("Upgrade failed.", e);
|
||||
throw new UpgradeFailedException("Upgrade failed. Details in log message.");
|
||||
} finally {
|
||||
cryptor.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void upgrade(Vault vault, Cryptor cryptor) throws UpgradeFailedException {
|
||||
Path path = vault.path().getValue();
|
||||
String oldVaultName = path.getFileName().toString();
|
||||
String newVaultName = StringUtils.removeEnd(oldVaultName, Vault.VAULT_FILE_EXTENSION);
|
||||
@@ -39,6 +76,7 @@ class UpgradeVersion3DropBundleExtension implements UpgradeInstruction {
|
||||
Files.move(path, path.resolveSibling(newVaultName));
|
||||
Platform.runLater(() -> {
|
||||
vault.setPath(newPath);
|
||||
settings.save();
|
||||
});
|
||||
} catch (IOException e) {
|
||||
LOG.error("Vault migration failed", e);
|
||||
@@ -49,7 +87,24 @@ class UpgradeVersion3DropBundleExtension implements UpgradeInstruction {
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(Vault vault) {
|
||||
return vault.path().getValue().getFileName().toString().endsWith(Vault.VAULT_FILE_EXTENSION);
|
||||
Path vaultPath = vault.path().getValue();
|
||||
if (vaultPath.toString().endsWith(Vault.VAULT_FILE_EXTENSION)) {
|
||||
final Path masterkeyFile = vaultPath.resolve(Constants.MASTERKEY_FILENAME);
|
||||
try {
|
||||
if (Files.isRegularFile(masterkeyFile)) {
|
||||
final String keyContents = new String(Files.readAllBytes(masterkeyFile), StandardCharsets.UTF_8);
|
||||
return keyContents.contains("\"version\":3") || keyContents.contains("\"version\": 3");
|
||||
} else {
|
||||
LOG.warn("Not a file: {}", masterkeyFile);
|
||||
return false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Could not determine, whether upgrade is applicable.", e);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.FileVisitor;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.crypto.engine.Cryptor;
|
||||
import org.cryptomator.filesystem.crypto.Constants;
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@Singleton
|
||||
class UpgradeVersion3to4 extends UpgradeStrategy {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion3to4.class);
|
||||
private static final Pattern BASE32_FOLLOWED_BY_UNDERSCORE_PATTERN = Pattern.compile("^(([A-Z2-7]{8})*[A-Z2-7=]{8})_");
|
||||
private static final int FILE_MIN_SIZE = 88; // vault version 3 files have a header of 88 bytes (assuming no chunks at all)
|
||||
|
||||
@Inject
|
||||
public UpgradeVersion3to4(Provider<Cryptor> cryptorProvider, Localization localization) {
|
||||
super(cryptorProvider, localization);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNotification(Vault vault) {
|
||||
return localization.getString("upgrade.version3to4.msg");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void upgrade(Vault vault, Cryptor cryptor) throws UpgradeFailedException {
|
||||
Path dataDir = vault.path().get().resolve("d");
|
||||
if (!Files.isDirectory(dataDir)) {
|
||||
return; // empty vault. no migration needed.
|
||||
}
|
||||
try {
|
||||
Files.walkFileTree(dataDir, new FileVisitor<Path>() {
|
||||
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
migrate(file, attrs);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
|
||||
throw exc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
});
|
||||
} catch (IOException e) {
|
||||
LOG.error("Migration failed.", e);
|
||||
throw new UpgradeFailedException(localization.getString("upgrade.version3to4.err.io"));
|
||||
}
|
||||
LOG.info("Migration finished.");
|
||||
}
|
||||
|
||||
private void migrate(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
String name = file.getFileName().toString();
|
||||
long size = attrs.size();
|
||||
Matcher m = BASE32_FOLLOWED_BY_UNDERSCORE_PATTERN.matcher(name);
|
||||
if (m.find(0) && size < FILE_MIN_SIZE) {
|
||||
String base32 = m.group(1);
|
||||
String suffix = name.substring(m.end());
|
||||
String renamed = "0" + base32 + (suffix.isEmpty() ? "" : " " + suffix);
|
||||
renameWithoutOverwriting(file, renamed);
|
||||
}
|
||||
}
|
||||
|
||||
private void renameWithoutOverwriting(Path path, String newName) throws IOException {
|
||||
Path newPath = path.resolveSibling(newName);
|
||||
for (int i = 2; Files.exists(newPath); i++) {
|
||||
newPath = path.resolveSibling(newName + " " + i);
|
||||
}
|
||||
Files.move(path, newPath);
|
||||
LOG.info("Renaming {} to {}", path, newPath.getFileName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(Vault vault) {
|
||||
final Path masterkeyFile = vault.path().getValue().resolve(Constants.MASTERKEY_FILENAME);
|
||||
try {
|
||||
if (Files.isRegularFile(masterkeyFile)) {
|
||||
final String keyContents = new String(Files.readAllBytes(masterkeyFile), StandardCharsets.UTF_8);
|
||||
return keyContents.contains("\"version\":3") || keyContents.contains("\"version\": 3");
|
||||
} else {
|
||||
LOG.warn("Not a file: {}", masterkeyFile);
|
||||
return false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Could not determine, whether upgrade is applicable.", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,7 +16,6 @@ import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.Normalizer;
|
||||
import java.text.Normalizer.Form;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -30,6 +29,7 @@ import org.cryptomator.common.LazyInitializer;
|
||||
import org.cryptomator.common.Optionals;
|
||||
import org.cryptomator.crypto.engine.InvalidPassphraseException;
|
||||
import org.cryptomator.filesystem.FileSystem;
|
||||
import org.cryptomator.filesystem.charsets.NormalizedNameFileSystem;
|
||||
import org.cryptomator.filesystem.crypto.CryptoFileSystemDelegate;
|
||||
import org.cryptomator.filesystem.crypto.CryptoFileSystemFactory;
|
||||
import org.cryptomator.filesystem.nio.NioFileSystem;
|
||||
@@ -63,9 +63,9 @@ public class Vault implements CryptoFileSystemDelegate {
|
||||
public static final String VAULT_FILE_EXTENSION = ".cryptomator";
|
||||
|
||||
private final ObjectProperty<Path> path;
|
||||
private final DeferredCloser closer;
|
||||
private final ShorteningFileSystemFactory shorteningFileSystemFactory;
|
||||
private final CryptoFileSystemFactory cryptoFileSystemFactory;
|
||||
private final DeferredCloser closer;
|
||||
private final BooleanProperty unlocked = new SimpleBooleanProperty();
|
||||
private final ObservableList<String> namesOfResourcesWithInvalidMac = FXThreads.observableListOnMainThread(FXCollections.observableArrayList());
|
||||
private final Set<String> whitelistedResourcesWithInvalidMac = new HashSet<>();
|
||||
@@ -81,9 +81,9 @@ public class Vault implements CryptoFileSystemDelegate {
|
||||
*/
|
||||
Vault(Path vaultDirectoryPath, ShorteningFileSystemFactory shorteningFileSystemFactory, CryptoFileSystemFactory cryptoFileSystemFactory, DeferredCloser closer) {
|
||||
this.path = new SimpleObjectProperty<Path>(vaultDirectoryPath);
|
||||
this.closer = closer;
|
||||
this.shorteningFileSystemFactory = shorteningFileSystemFactory;
|
||||
this.cryptoFileSystemFactory = cryptoFileSystemFactory;
|
||||
this.closer = closer;
|
||||
|
||||
try {
|
||||
setMountName(name().getValue());
|
||||
@@ -126,7 +126,8 @@ public class Vault implements CryptoFileSystemDelegate {
|
||||
FileSystem fs = getNioFileSystem();
|
||||
FileSystem shorteningFs = shorteningFileSystemFactory.get(fs);
|
||||
FileSystem cryptoFs = cryptoFileSystemFactory.unlockExisting(shorteningFs, passphrase, this);
|
||||
StatsFileSystem statsFs = new StatsFileSystem(cryptoFs);
|
||||
FileSystem normalizingFs = new NormalizedNameFileSystem(cryptoFs, SystemUtils.IS_OS_MAC_OSX ? Form.NFD : Form.NFC);
|
||||
StatsFileSystem statsFs = new StatsFileSystem(normalizingFs);
|
||||
statsFileSystem = Optional.of(statsFs);
|
||||
String contextPath = StringUtils.prependIfMissing(mountName, "/");
|
||||
Frontend frontend = frontendFactory.create(statsFs, contextPath);
|
||||
@@ -165,16 +166,6 @@ public class Vault implements CryptoFileSystemDelegate {
|
||||
Optionals.ifPresent(filesystemFrontend.get(), Frontend::unmount);
|
||||
}
|
||||
|
||||
public boolean needsUpgrade() {
|
||||
return availableUpgrade().isPresent();
|
||||
}
|
||||
|
||||
public Optional<UpgradeInstruction> availableUpgrade() {
|
||||
return Arrays.stream(UpgradeInstruction.AVAILABLE_INSTRUCTIONS).filter(instruction -> {
|
||||
return instruction.isApplicable(this);
|
||||
}).findAny();
|
||||
}
|
||||
|
||||
// ******************************************************************************
|
||||
// Delegate methods
|
||||
// ********************************************************************************/
|
||||
|
||||
@@ -93,8 +93,8 @@ public class Settings implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPortValid(int port) {
|
||||
return port == DEFAULT_PORT || port >= MIN_PORT && port <= MAX_PORT;
|
||||
public boolean isPortValid(int port) {
|
||||
return port == DEFAULT_PORT || port >= MIN_PORT && port <= MAX_PORT || port == 0;
|
||||
}
|
||||
|
||||
public boolean shouldUseIpv6() {
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*******************************************************************************
|
||||
* 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:
|
||||
* Jean-Noël Charon - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.util;
|
||||
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.text.Text;
|
||||
|
||||
public class DialogBuilderUtil {
|
||||
|
||||
public DialogBuilderUtil() {
|
||||
}
|
||||
|
||||
public static Alert buildInformationDialog(String title, String header, String content, ButtonType defaultButton) {
|
||||
return buildDialog(title, header, content, Alert.AlertType.INFORMATION, defaultButton);
|
||||
}
|
||||
|
||||
public static Alert buildWarningDialog(String title, String header, String content, ButtonType defaultButton) {
|
||||
return buildDialog(title, header, content, Alert.AlertType.WARNING, defaultButton);
|
||||
}
|
||||
|
||||
public static Alert buildErrorDialog(String title, String header, String content, ButtonType defaultButton) {
|
||||
return buildDialog(title, header, content, Alert.AlertType.ERROR, defaultButton);
|
||||
}
|
||||
|
||||
public static Alert buildConfirmationDialog(String title, String header, String content, ButtonType defaultButton) {
|
||||
return buildDialog(title, header, content, Alert.AlertType.CONFIRMATION, defaultButton);
|
||||
}
|
||||
|
||||
private static Alert buildDialog(String title, String header, String content, Alert.AlertType type, ButtonType defaultButton) {
|
||||
Text contentText = new Text(content);
|
||||
contentText.setWrappingWidth(360.0);
|
||||
|
||||
Alert alert = new Alert(type);
|
||||
alert.setTitle(title);
|
||||
alert.setHeaderText(header);
|
||||
alert.getDialogPane().setContent(contentText);
|
||||
|
||||
alert.getDialogPane().getButtonTypes().stream().forEach(buttonType -> {
|
||||
Button btn = (Button) alert.getDialogPane().lookupButton(buttonType);
|
||||
btn.setDefaultButton(buttonType.equals(defaultButton));
|
||||
});
|
||||
|
||||
return alert;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*******************************************************************************
|
||||
* 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:
|
||||
* Jean-Noël Charon - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
|
||||
import com.nulabinc.zxcvbn.Zxcvbn;
|
||||
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.layout.Background;
|
||||
import javafx.scene.layout.BackgroundFill;
|
||||
import javafx.scene.layout.CornerRadii;
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
@Singleton
|
||||
public class PasswordStrengthUtil {
|
||||
|
||||
private final Zxcvbn zxcvbn;
|
||||
private final List<String> sanitizedInputs;
|
||||
private final Localization localization;
|
||||
|
||||
@Inject
|
||||
public PasswordStrengthUtil(Localization localization) {
|
||||
this.localization = localization;
|
||||
this.zxcvbn = new Zxcvbn();
|
||||
this.sanitizedInputs = new ArrayList<>();
|
||||
this.sanitizedInputs.add("cryptomator");
|
||||
}
|
||||
|
||||
public int computeRate(String password) {
|
||||
if (StringUtils.isEmpty(password)) {
|
||||
return -1;
|
||||
} else {
|
||||
return zxcvbn.measure(password, sanitizedInputs).getScore();
|
||||
}
|
||||
}
|
||||
|
||||
public Color getStrengthColor(Number score) {
|
||||
switch (score.intValue()) {
|
||||
case 0:
|
||||
return Color.web("#e74c3c");
|
||||
case 1:
|
||||
return Color.web("#e67e22");
|
||||
case 2:
|
||||
return Color.web("#f1c40f");
|
||||
case 3:
|
||||
return Color.web("#40d47e");
|
||||
case 4:
|
||||
return Color.web("#27ae60");
|
||||
default:
|
||||
return Color.web("#ffffff", 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
public Background getBackgroundWithStrengthColor(Number score) {
|
||||
Color c = this.getStrengthColor(score);
|
||||
BackgroundFill fill = new BackgroundFill(c, CornerRadii.EMPTY, Insets.EMPTY);
|
||||
return new Background(fill);
|
||||
}
|
||||
|
||||
public Background getBackgroundWithStrengthColor(Number score, Number threshold) {
|
||||
return score.intValue() >= threshold.intValue() ? getBackgroundWithStrengthColor(score) : getBackgroundWithStrengthColor(-1);
|
||||
}
|
||||
|
||||
public String getStrengthDescription(Number score) {
|
||||
String key = "initialize.messageLabel.passwordStrength." + score.intValue();
|
||||
if (localization.containsKey(key)) {
|
||||
return localization.getString(key);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 34 KiB |
@@ -5,6 +5,7 @@
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
* Jean-Noël Charon - implementation of the dialog css
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -50,6 +51,10 @@
|
||||
-fx-font-family: Ionicons;
|
||||
}
|
||||
|
||||
.caption-label {
|
||||
-fx-font-size: 0.9em;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Hyperlinks *
|
||||
@@ -76,7 +81,7 @@
|
||||
-fx-pref-height: 25px;
|
||||
-fx-background-color: COLOR_BORDER, COLOR_VGRAD_LIGHT;
|
||||
-fx-background-insets: 0, 1;
|
||||
-fx-padding: 4px 8px 4px 8px;
|
||||
-fx-padding: 4px 12px 6px 12px;
|
||||
-fx-text-fill: COLOR_TEXT;
|
||||
-fx-alignment: CENTER;
|
||||
}
|
||||
@@ -440,4 +445,65 @@
|
||||
-fx-stroke-width: 2px;
|
||||
}
|
||||
.default-color0.chart-series-line { -fx-stroke: COLOR_CHART_GREEN; }
|
||||
.default-color1.chart-series-line { -fx-stroke: COLOR_CHART_RED; }
|
||||
.default-color1.chart-series-line { -fx-stroke: COLOR_CHART_RED; }
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Dialog *
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
.dialog-pane {
|
||||
-fx-background-color: COLOR_BACKGROUND;
|
||||
-fx-padding: 20px 20px 20px 96px;
|
||||
|
||||
-fx-background-image: url("/img/dialog-appicon.png");
|
||||
-fx-background-repeat: no-repeat;
|
||||
-fx-background-position: 20px 20px;
|
||||
}
|
||||
|
||||
/* HEADER */
|
||||
.dialog-pane:header .header-panel {
|
||||
-fx-padding: 0 0 12px 0;
|
||||
}
|
||||
|
||||
/* TITLE */
|
||||
.dialog-pane:header .header-panel .label {
|
||||
-fx-font-weight: bold;
|
||||
-fx-wrap-text: true;
|
||||
-fx-font-size: 14px;
|
||||
}
|
||||
|
||||
/* CONTENT LABEL */
|
||||
.dialog-pane > .content {
|
||||
-fx-alignment: top-left;
|
||||
-fx-wrap-text: true;
|
||||
-fx-font-size: 12px;
|
||||
}
|
||||
|
||||
/* BUTTONS */
|
||||
.dialog-pane > .button-bar > .container {
|
||||
-fx-padding: 20px 0 0 0;
|
||||
}
|
||||
|
||||
.alert.confirmation.dialog-pane,
|
||||
.text-input-dialog.dialog-pane,
|
||||
.choice-dialog.dialog-pane {
|
||||
-fx-padding: 20px 20px 20px 80px;
|
||||
-fx-background-image: url("/img/dialog-confirm.png");
|
||||
}
|
||||
|
||||
.alert.information.dialog-pane {
|
||||
-fx-padding: 20px 20px 20px 80px;
|
||||
-fx-background-image: url("/img/dialog-information.png");
|
||||
}
|
||||
|
||||
.alert.error.dialog-pane {
|
||||
-fx-padding: 20px 20px 20px 80px;
|
||||
-fx-background-image: url("/img/dialog-error.png");
|
||||
}
|
||||
|
||||
.alert.warning.dialog-pane {
|
||||
-fx-padding: 20px 20px 20px 80px;
|
||||
-fx-background-image: url("/img/dialog-warning.png");
|
||||
}
|
||||
@@ -5,11 +5,12 @@
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
* Jean-Noël Charon - implementation of the dialog css
|
||||
*
|
||||
*/
|
||||
|
||||
.root {
|
||||
-fx-font-family: 'lucida-grande';
|
||||
-fx-font-family: 'lucida-grande', sans-serif;
|
||||
-fx-font-smoothing-type: lcd;
|
||||
-fx-font-size: 13px;
|
||||
|
||||
@@ -49,6 +50,10 @@
|
||||
-fx-font-family: Ionicons;
|
||||
}
|
||||
|
||||
.caption-label {
|
||||
-fx-font-size: 0.9em;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Hyperlinks *
|
||||
@@ -530,4 +535,53 @@
|
||||
-fx-stroke-width: 2px;
|
||||
}
|
||||
.default-color0.chart-series-line { -fx-stroke: COLOR_CHART_GREEN; }
|
||||
.default-color1.chart-series-line { -fx-stroke: COLOR_CHART_RED; }
|
||||
.default-color1.chart-series-line { -fx-stroke: COLOR_CHART_RED; }
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Dialog *
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
.dialog-pane {
|
||||
-fx-background-color: COLOR_BACKGROUND;
|
||||
-fx-padding: 20px 20px 20px 96px;
|
||||
|
||||
-fx-background-image: url("/img/dialog-appicon.png");
|
||||
-fx-background-repeat: no-repeat;
|
||||
-fx-background-position: 20px 20px;
|
||||
}
|
||||
|
||||
/* HEADER */
|
||||
.dialog-pane:header .header-panel {
|
||||
-fx-padding: 0 0 12px 0;
|
||||
}
|
||||
|
||||
/* TITLE */
|
||||
.dialog-pane:header .header-panel .label {
|
||||
-fx-font-weight: bold;
|
||||
-fx-wrap-text: true;
|
||||
}
|
||||
|
||||
/* CONTENT LABEL */
|
||||
.dialog-pane > .content {
|
||||
-fx-alignment: top-left;
|
||||
-fx-wrap-text: true;
|
||||
-fx-font-size: 11px;
|
||||
-fx-line-spacing: 1.0;
|
||||
}
|
||||
|
||||
/* BUTTONS */
|
||||
.dialog-pane > .button-bar > .container {
|
||||
-fx-padding: 12px 0 0 0;
|
||||
}
|
||||
|
||||
.dialog-pane > .button-bar .button:default {
|
||||
-fx-background-color: COLOR_HGRAD_BTN_DEF_BORDER, COLOR_HGRAD_BTN_DEF_BACKGROUND;
|
||||
-fx-text-fill: #FFF;
|
||||
}
|
||||
|
||||
.dialog-pane > .button-bar .button:default:armed {
|
||||
-fx-background-color: COLOR_HGRAD_BTN_ARMED_BORDER, COLOR_HGRAD_BTN_ARMED_BACKGROUND;
|
||||
-fx-text-fill: #FFF;
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
* Jean-Noël Charon - implementation of the dialog css
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -24,6 +25,10 @@
|
||||
COLOR_CHART_GREEN: #A1CD5f;
|
||||
COLOR_CHART_RED: #C75050;
|
||||
|
||||
COLOR_HGRAD_BTN_BACKGROUND: linear-gradient(to bottom, #F0F0F0 0%, #E5E5E5 100%);
|
||||
COLOR_HGRAD_BTN_DISABLED_BORDER: linear-gradient(to bottom, #D2D2D2 0%, #C4C4C4 100%);
|
||||
COLOR_HGRAD_BTN_ARMED_BACKGROUND: linear-gradient(to bottom, #DAECFC 0%, #C4E0FC 100%);
|
||||
|
||||
-fx-background-color: COLOR_BACKGROUND;
|
||||
-fx-text-fill: COLOR_TEXT;
|
||||
}
|
||||
@@ -42,6 +47,10 @@
|
||||
-fx-font-family: Ionicons;
|
||||
}
|
||||
|
||||
.caption-label {
|
||||
-fx-font-size: 0.9em;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Hyperlinks *
|
||||
@@ -66,7 +75,7 @@
|
||||
.button,
|
||||
.toggle-button {
|
||||
-fx-pref-height: 27px;
|
||||
-fx-background-color: COLOR_BORDER, linear-gradient(to bottom, #F0F0F0 0%, #E5E5E5 100%);
|
||||
-fx-background-color: COLOR_BORDER, COLOR_HGRAD_BTN_BACKGROUND;
|
||||
-fx-background-insets: 0, 1;
|
||||
-fx-padding: 2px 12px 2px 12px;
|
||||
-fx-text-fill: COLOR_TEXT;
|
||||
@@ -76,12 +85,12 @@
|
||||
}
|
||||
|
||||
.button:default {
|
||||
-fx-background-color: COLOR_BORDER_FOCUS, linear-gradient(to bottom, #F0F0F0 0%, #E5E5E5 100%);
|
||||
-fx-background-color: COLOR_BORDER_FOCUS, COLOR_HGRAD_BTN_BACKGROUND;
|
||||
}
|
||||
|
||||
.button:disabled,
|
||||
.button:default:disabled {
|
||||
-fx-background-color: linear-gradient(to bottom, #D2D2D2 0%, #C4C4C4 100%), #F2F2F2;
|
||||
-fx-background-color: COLOR_HGRAD_BTN_DISABLED_BORDER, #F2F2F2;
|
||||
-fx-text-fill: #8B8B8B;
|
||||
}
|
||||
|
||||
@@ -89,7 +98,7 @@
|
||||
.button:default:armed,
|
||||
.toggle-button:armed,
|
||||
.toggle-button:selected {
|
||||
-fx-background-color: COLOR_BORDER_FOCUS, linear-gradient(to bottom, #DAECFC 0%, #C4E0FC 100%);
|
||||
-fx-background-color: COLOR_BORDER_FOCUS, COLOR_HGRAD_BTN_ARMED_BACKGROUND;
|
||||
}
|
||||
|
||||
.button:focused,
|
||||
@@ -512,4 +521,73 @@
|
||||
-fx-stroke-width: 2px;
|
||||
}
|
||||
.default-color0.chart-series-line { -fx-stroke: COLOR_CHART_GREEN; }
|
||||
.default-color1.chart-series-line { -fx-stroke: COLOR_CHART_RED; }
|
||||
.default-color1.chart-series-line { -fx-stroke: COLOR_CHART_RED; }
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Dialog *
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
.dialog-pane {
|
||||
-fx-background-color: COLOR_BACKGROUND;
|
||||
-fx-padding: 20px 20px 20px 96px;
|
||||
|
||||
-fx-background-image: url("/img/dialog-appicon.png");
|
||||
-fx-background-repeat: no-repeat;
|
||||
-fx-background-position: 20px 20px;
|
||||
}
|
||||
|
||||
/* HEADER */
|
||||
.dialog-pane:header .header-panel {
|
||||
-fx-padding: 0 0 12px 0;
|
||||
}
|
||||
|
||||
/* TITLE */
|
||||
.dialog-pane:header .header-panel .label {
|
||||
-fx-font-weight: bold;
|
||||
-fx-wrap-text: true;
|
||||
}
|
||||
|
||||
/* CONTENT LABEL */
|
||||
.dialog-pane > .content {
|
||||
-fx-alignment: top-left;
|
||||
-fx-wrap-text: true;
|
||||
-fx-font-size: 11px;
|
||||
-fx-line-spacing: 1.0;
|
||||
}
|
||||
|
||||
/* BUTTONS */
|
||||
.dialog-pane > .button-bar > .container {
|
||||
-fx-padding: 20px 0 0 0;
|
||||
}
|
||||
|
||||
.dialog-pane > .button-bar .button:default {
|
||||
-fx-background-color: COLOR_BORDER_FOCUS, COLOR_HGRAD_BTN_BACKGROUND;
|
||||
}
|
||||
|
||||
.dialog-pane > .button-bar .button:default:armed {
|
||||
-fx-background-color: COLOR_BORDER_FOCUS, COLOR_HGRAD_BTN_ARMED_BACKGROUND;
|
||||
}
|
||||
|
||||
.alert.confirmation.dialog-pane,
|
||||
.text-input-dialog.dialog-pane,
|
||||
.choice-dialog.dialog-pane {
|
||||
-fx-padding: 20px 20px 20px 80px;
|
||||
-fx-background-image: url("/img/dialog-confirm.png");
|
||||
}
|
||||
|
||||
.alert.information.dialog-pane {
|
||||
-fx-padding: 20px 20px 20px 80px;
|
||||
-fx-background-image: url("/img/dialog-information.png");
|
||||
}
|
||||
|
||||
.alert.error.dialog-pane {
|
||||
-fx-padding: 20px 20px 20px 80px;
|
||||
-fx-background-image: url("/img/dialog-error.png");
|
||||
}
|
||||
|
||||
.alert.warning.dialog-pane {
|
||||
-fx-padding: 20px 20px 20px 80px;
|
||||
-fx-background-image: url("/img/dialog-warning.png");
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
Contributors:
|
||||
Sebastian Stenzel - initial API and implementation
|
||||
Jean-Noël Charon - password strength meter
|
||||
-->
|
||||
<?import java.net.URL?>
|
||||
<?import java.lang.String?>
|
||||
@@ -20,7 +21,9 @@
|
||||
<?import javafx.scene.text.TextFlow?>
|
||||
<?import javafx.scene.control.Hyperlink?>
|
||||
<?import javafx.scene.text.Text?>
|
||||
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
|
||||
<GridPane vgap="12.0" hgap="12.0" prefWidth="400.0" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
|
||||
<padding>
|
||||
@@ -46,10 +49,25 @@
|
||||
<SecPasswordField fx:id="retypePasswordField" GridPane.rowIndex="2" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
|
||||
|
||||
<!-- Row 3 -->
|
||||
<Button fx:id="changePasswordButton" text="%changePassword.button.change" defaultButton="true" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="RIGHT" prefWidth="150.0" onAction="#didClickChangePasswordButton" disable="true" cacheShape="true" cache="true"/>
|
||||
<VBox GridPane.columnIndex="1" GridPane.rowIndex="3" spacing="6.0">
|
||||
<HBox spacing="6.0" prefHeight="6.0" cacheShape="true" cache="true">
|
||||
<Region HBox.hgrow="ALWAYS" fx:id="passwordStrengthLevel0" cacheShape="true" cache="true" />
|
||||
<Region HBox.hgrow="ALWAYS" fx:id="passwordStrengthLevel1" cacheShape="true" cache="true" />
|
||||
<Region HBox.hgrow="ALWAYS" fx:id="passwordStrengthLevel2" cacheShape="true" cache="true" />
|
||||
<Region HBox.hgrow="ALWAYS" fx:id="passwordStrengthLevel3" cacheShape="true" cache="true" />
|
||||
<Region HBox.hgrow="ALWAYS" fx:id="passwordStrengthLevel4" cacheShape="true" cache="true" />
|
||||
</HBox>
|
||||
<Label fx:id="passwordStrengthLabel" styleClass="caption-label" cache="true" cacheShape="true" text="" />
|
||||
</VBox>
|
||||
|
||||
<!-- Row 4 -->
|
||||
<TextFlow GridPane.rowIndex="4" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true">
|
||||
<Label text="%initialize.label.doNotForget" wrapText="true" cache="true" cacheShape="true" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="4" />
|
||||
|
||||
<!-- Row 5 -->
|
||||
<Button fx:id="changePasswordButton" text="%changePassword.button.change" defaultButton="true" GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="RIGHT" prefWidth="150.0" onAction="#didClickChangePasswordButton" disable="true" cacheShape="true" cache="true"/>
|
||||
|
||||
<!-- Row 6 -->
|
||||
<TextFlow GridPane.rowIndex="6" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true">
|
||||
<children>
|
||||
<Text fx:id="messageText" cache="true" />
|
||||
<Hyperlink fx:id="downloadsPageLink" text="%changePassword.label.downloadsPageLink" visible="false" onAction="#didClickDownloadsLink" cacheShape="true" cache="true" />
|
||||
|
||||
@@ -6,15 +6,22 @@
|
||||
|
||||
Contributors:
|
||||
Sebastian Stenzel - initial API and implementation
|
||||
Jean-Noël Charon - password strength meter
|
||||
-->
|
||||
|
||||
<?import java.lang.*?>
|
||||
<?import java.net.URL?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import org.cryptomator.ui.controls.SecPasswordField?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.layout.GridPane?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.layout.ColumnConstraints?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ProgressBar?>
|
||||
<?import javafx.scene.control.ProgressIndicator?>
|
||||
<?import org.cryptomator.ui.controls.SecPasswordField?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
|
||||
<GridPane vgap="12.0" hgap="12.0" prefWidth="400.0" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
|
||||
<padding>
|
||||
@@ -30,17 +37,31 @@
|
||||
<!-- Row 0 -->
|
||||
<Label GridPane.rowIndex="0" GridPane.columnIndex="0" text="%initialize.label.password" cacheShape="true" cache="true" />
|
||||
<SecPasswordField fx:id="passwordField" GridPane.rowIndex="0" GridPane.columnIndex="1" cacheShape="true" cache="true" />
|
||||
|
||||
|
||||
<!-- Row 1 -->
|
||||
<Label GridPane.rowIndex="1" GridPane.columnIndex="0" text="%initialize.label.retypePassword" cacheShape="true" cache="true" />
|
||||
<SecPasswordField fx:id="retypePasswordField" GridPane.rowIndex="1" GridPane.columnIndex="1" cacheShape="true" cache="true" />
|
||||
|
||||
<!-- Row 2 -->
|
||||
<Button fx:id="okButton" defaultButton="true" GridPane.rowIndex="2" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="RIGHT" text="%initialize.button.ok" prefWidth="150.0" onAction="#initializeVault" focusTraversable="false" disable="true" cacheShape="true" cache="true" />
|
||||
|
||||
<VBox GridPane.columnIndex="1" GridPane.rowIndex="2" spacing="6.0">
|
||||
<HBox spacing="6.0" prefHeight="6.0" cacheShape="true" cache="true">
|
||||
<Region HBox.hgrow="ALWAYS" fx:id="passwordStrengthLevel0" cacheShape="true" cache="true" />
|
||||
<Region HBox.hgrow="ALWAYS" fx:id="passwordStrengthLevel1" cacheShape="true" cache="true" />
|
||||
<Region HBox.hgrow="ALWAYS" fx:id="passwordStrengthLevel2" cacheShape="true" cache="true" />
|
||||
<Region HBox.hgrow="ALWAYS" fx:id="passwordStrengthLevel3" cacheShape="true" cache="true" />
|
||||
<Region HBox.hgrow="ALWAYS" fx:id="passwordStrengthLevel4" cacheShape="true" cache="true" />
|
||||
</HBox>
|
||||
<Label fx:id="passwordStrengthLabel" styleClass="caption-label" cache="true" cacheShape="true" text="" />
|
||||
</VBox>
|
||||
|
||||
<!-- Row 3 -->
|
||||
<Label fx:id="messageLabel" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true" />
|
||||
<Label text="%initialize.label.doNotForget" wrapText="true" cache="true" cacheShape="true" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="3" />
|
||||
|
||||
<!-- Row 4 -->
|
||||
<Button fx:id="okButton" cache="true" cacheShape="true" defaultButton="true" disable="true" focusTraversable="false" onAction="#initializeVault" prefWidth="150.0" text="%initialize.button.ok" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="RIGHT" GridPane.rowIndex="4" />
|
||||
|
||||
<!-- Row 5 -->
|
||||
<Label fx:id="messageLabel" cache="true" cacheShape="true" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="5" />
|
||||
|
||||
</children>
|
||||
</GridPane>
|
||||
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<MenuItem text="%main.directoryList.contextMenu.remove" onAction="#didClickRemoveSelectedEntry">
|
||||
<graphic><Label text="" styleClass="ionicons"/></graphic>
|
||||
</MenuItem>
|
||||
<MenuItem text="%main.directoryList.contextMenu.changePassword" onAction="#didClickChangePassword">
|
||||
<MenuItem text="%main.directoryList.contextMenu.changePassword" fx:id="changePasswordMenuItem" onAction="#didClickChangePassword">
|
||||
<graphic><Label text="" styleClass="ionicons"/></graphic>
|
||||
</MenuItem>
|
||||
</items>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<TextField GridPane.rowIndex="1" GridPane.columnIndex="1" fx:id="portField" cacheShape="true" cache="true" promptText="%settings.port.prompt" />
|
||||
|
||||
<!-- Row 2 -->
|
||||
<Label GridPane.rowIndex="2" GridPane.columnIndex="0" text="%settings.useipv6.label" cacheShape="true" cache="true" />
|
||||
<Label GridPane.rowIndex="2" GridPane.columnIndex="0" fx:id="useIpv6Label" text="%settings.useipv6.label" cacheShape="true" cache="true" />
|
||||
<CheckBox GridPane.rowIndex="2" GridPane.columnIndex="1" fx:id="useIpv6Checkbox" cacheShape="true" cache="true" />
|
||||
</children>
|
||||
</GridPane>
|
||||
|
||||
@@ -11,17 +11,38 @@
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ProgressIndicator?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import org.cryptomator.ui.controls.SecPasswordField?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.layout.GridPane?>
|
||||
<?import javafx.scene.layout.ColumnConstraints?>
|
||||
<?import javafx.scene.layout.Pane?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
|
||||
<VBox prefWidth="400.0" prefHeight="400.0" spacing="24.0" alignment="CENTER" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
|
||||
<GridPane vgap="12.0" hgap="12.0" prefWidth="400.0" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
|
||||
<padding>
|
||||
<Insets top="24.0" right="12.0" bottom="24.0" left="12.0" />
|
||||
</padding>
|
||||
|
||||
<Label fx:id="upgradeLabel" textAlignment="CENTER" wrapText="true"/>
|
||||
<columnConstraints>
|
||||
<ColumnConstraints percentWidth="38.2" />
|
||||
<ColumnConstraints percentWidth="61.8" />
|
||||
</columnConstraints>
|
||||
|
||||
<Button fx:id="upgradeButton" text="%upgrade.button" prefWidth="150.0" onAction="#didClickUpgradeButton" cacheShape="true" cache="true" />
|
||||
<children>
|
||||
<Label fx:id="upgradeLabel" wrapText="true" GridPane.rowIndex="0" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true" />
|
||||
|
||||
<ProgressIndicator progress="-1" fx:id="progressIndicator" visible="false" cacheShape="true" cache="true" cacheHint="SPEED" />
|
||||
|
||||
<Label fx:id="errorLabel" textAlignment="CENTER" wrapText="true"/>
|
||||
</VBox>
|
||||
<Pane prefHeight="12.0" GridPane.rowIndex="1" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true">
|
||||
</Pane>
|
||||
|
||||
<Label text="%unlock.label.password" cacheShape="true" cache="true" GridPane.rowIndex="2" GridPane.columnIndex="0" />
|
||||
<SecPasswordField fx:id="passwordField" cacheShape="true" cache="true" GridPane.rowIndex="2" GridPane.columnIndex="1" />
|
||||
|
||||
<HBox alignment="CENTER_RIGHT" GridPane.hgrow="ALWAYS" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true">
|
||||
<Button fx:id="upgradeButton" text="%upgrade.button" prefWidth="150.0" onAction="#didClickUpgradeButton" cacheShape="true" cache="true" />
|
||||
</HBox>
|
||||
|
||||
<ProgressIndicator progress="-1" fx:id="progressIndicator" visible="false" cacheShape="true" cache="true" cacheHint="SPEED" GridPane.rowIndex="4" GridPane.columnIndex="0" GridPane.columnSpan="2" />
|
||||
|
||||
<Label fx:id="errorLabel" wrapText="true" GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true" />
|
||||
</children>
|
||||
</GridPane>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<Label fx:id="checkForUpdatesStatus" cacheShape="true" cache="true" />
|
||||
<ProgressIndicator fx:id="checkForUpdatesIndicator" progress="-1" prefWidth="15.0" prefHeight="15.0" cacheShape="true" cache="true" cacheHint="SPEED" />
|
||||
</HBox>
|
||||
<Hyperlink alignment="CENTER" fx:id="updateLink" onAction="#didClickUpdateLink" cacheShape="true" cache="true" />
|
||||
<Hyperlink alignment="CENTER" fx:id="updateLink" onAction="#didClickUpdateLink" cacheShape="true" cache="true" disable="true" />
|
||||
</VBox>
|
||||
|
||||
<ImageView fitHeight="200.0" preserveRatio="true" smooth="false" cache="true" style="-fx-background-color: green;">
|
||||
|
||||
BIN
main/ui/src/main/resources/img/dialog-appicon.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
main/ui/src/main/resources/img/dialog-appicon@2x.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
main/ui/src/main/resources/img/dialog-confirm.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
main/ui/src/main/resources/img/dialog-error.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
main/ui/src/main/resources/img/dialog-information.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
main/ui/src/main/resources/img/dialog-warning.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
@@ -1,13 +1,3 @@
|
||||
# Copyright (c) 2016 The Cryptomator Contributors
|
||||
# This file is licensed under the terms of the MIT license.
|
||||
# See the LICENSE.txt file for more info.
|
||||
#
|
||||
# Contributors:
|
||||
# Markus Kreusch
|
||||
# Michael Schmetter
|
||||
# Sebastian Wiesendahl
|
||||
# Tim Marius Wunderlich
|
||||
|
||||
app.name = Cryptomator
|
||||
# main.fxml
|
||||
main.emptyListInstructions = Klicken Sie hier, um neue Tresore hinzuzufügen
|
||||
@@ -16,8 +6,8 @@ main.directoryList.contextMenu.changePassword = Passwort ändern
|
||||
main.addDirectory.contextMenu.new = Tresor erstellen
|
||||
main.addDirectory.contextMenu.open = Tresor öffnen
|
||||
# welcome.fxml
|
||||
welcome.checkForUpdates.label.currentlyChecking = Auf Updates prüfen...
|
||||
welcome.newVersionMessage = Version %s kann heruntergeladen werden. Aktuelle Version %s.
|
||||
welcome.checkForUpdates.label.currentlyChecking = Prüfe auf Updates...
|
||||
welcome.newVersionMessage = Version %1$s kann heruntergeladen werden. Momentane Version %2$s.
|
||||
# initialize.fxml
|
||||
initialize.label.password = Passwort
|
||||
initialize.label.retypePassword = Passwort bestätigen
|
||||
@@ -42,7 +32,6 @@ unlock.button.advancedOptions.hide = Weniger Optionen
|
||||
unlock.choicebox.winDriveLetter.auto = Automatisch ermitteln
|
||||
unlock.errorMessage.wrongPassword = Falsches Passwort
|
||||
unlock.errorMessage.mountingFailed = Verbindung fehlgeschlagen. Details in der Log-Datei.
|
||||
unlock.errorMessage.unsupportedKeyLengthInstallJCE = Entschlüsselung fehlgeschlagen. Bitte die Oracle JCE Unlimited Strength Policy installieren.
|
||||
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Tresor nicht unterstützt. Der Tresor wurde mit einer älteren Version von Cryptomator erstellt.
|
||||
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Tresor nicht unterstützt. Der Tresor wurde mit einer neueren Version von Cryptomator erstellt.
|
||||
unlock.messageLabel.startServerFailed = Starten des WebDAV-Servers fehlgeschlagen.
|
||||
@@ -54,9 +43,6 @@ changePassword.label.downloadsPageLink = Alle Cryptomator Versionen
|
||||
changePassword.button.change = Passwort ändern
|
||||
changePassword.errorMessage.wrongPassword = Falsches Passwort
|
||||
changePassword.errorMessage.decryptionFailed = Entschlüsselung fehlgeschlagen
|
||||
changePassword.errorMessage.unsupportedKeyLengthInstallJCE = Entschlüsselung fehlgeschlagen. Bitte die Oracle JCE Unlimited Strength Policy installieren.
|
||||
changePassword.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Tresor nicht unterstützt. Der Tresor wurde mit einer älteren Version von Cryptomator erstellt.
|
||||
changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault = Tresor nicht unterstützt. Der Tresor wurde mit einer neueren Version von Cryptomator erstellt.
|
||||
changePassword.infoMessage.success = Passwort geändert
|
||||
# unlocked.fxml
|
||||
unlocked.button.lock = Tresor sperren
|
||||
@@ -86,3 +72,14 @@ tray.menu.quit = Beenden
|
||||
tray.infoMsg.title = Cryptomator läuft noch
|
||||
tray.infoMsg.msg = Cryptomator läuft noch. Mit dem Tray-Icon beenden.
|
||||
tray.infoMsg.msg.osx = Cryptomator läuft noch. Über die Menüleiste beenden.
|
||||
initialize.messageLabel.passwordStrength.0 = Sehr schwach
|
||||
initialize.messageLabel.passwordStrength.1 = Schwach
|
||||
initialize.messageLabel.passwordStrength.2 = Mittel
|
||||
initialize.messageLabel.passwordStrength.3 = Stark
|
||||
initialize.messageLabel.passwordStrength.4 = Sehr stark
|
||||
initialize.label.doNotForget = WICHTIG\: Falls Sie Ihr Passwort vergessen, gibt es keine Möglichkeit Ihre Daten wiederherzustellen.
|
||||
main.directoryList.remove.confirmation.title = Tresor entfernen
|
||||
main.directoryList.remove.confirmation.header = Möchten Sie diesen Tresor wirklich entfernen?
|
||||
main.directoryList.remove.confirmation.content = Dieser Tresor wird nur aus der Liste entfernt. Um den Tresor unwiderruflich zu löschen, entfernen Sie bitte die Dateien aus Ihrem Dateisystem.
|
||||
upgrade.version3to4.msg = Dieser Tresor muss auf ein neueres Format aktualisiert werden.\nVerschlüsselte Ordnernamen werden dabei aktualisiert.\nStellen Sie bitte sicher, dass derzeit keine Synchronisation stattfindet.
|
||||
upgrade.version3to4.err.io = Migration aufgrund eines I/O-Fehlers fehlgeschlagen.\nWeitere Informationen in der Log-Datei.
|
||||
@@ -13,17 +13,26 @@ main.directoryList.contextMenu.remove=Remove from list
|
||||
main.directoryList.contextMenu.changePassword=Change password
|
||||
main.addDirectory.contextMenu.new=Create new vault
|
||||
main.addDirectory.contextMenu.open=Open existing vault
|
||||
main.directoryList.remove.confirmation.title=Remove Vault
|
||||
main.directoryList.remove.confirmation.header=Do you really want to remove this vault?
|
||||
main.directoryList.remove.confirmation.content=The vault will only be removed from the list. To permanently delete it, please delete the files from your filesystem.
|
||||
|
||||
# welcome.fxml
|
||||
welcome.checkForUpdates.label.currentlyChecking=Checking for Updates...
|
||||
welcome.newVersionMessage=Version %s can be downloaded. This is %s.
|
||||
welcome.newVersionMessage=Version %1$s can be downloaded. This is %2$s.
|
||||
|
||||
# initialize.fxml
|
||||
initialize.label.password=Password
|
||||
initialize.label.retypePassword=Retype password
|
||||
initialize.button.ok=Create vault
|
||||
initialize.messageLabel.alreadyInitialized=Vault already initialized
|
||||
initialize.messageLabel.initializationFailed=Could not initialize vault. See logfile for details.
|
||||
initialize.messageLabel.initializationFailed=Could not initialize vault. See log file for details.
|
||||
initialize.messageLabel.passwordStrength.0=Very weak
|
||||
initialize.messageLabel.passwordStrength.1=Weak
|
||||
initialize.messageLabel.passwordStrength.2=Fair
|
||||
initialize.messageLabel.passwordStrength.3=Strong
|
||||
initialize.messageLabel.passwordStrength.4=Very strong
|
||||
initialize.label.doNotForget=IMPORTANT: If you forget your password, there is no way to recover your data.
|
||||
|
||||
# notfound.fxml
|
||||
notfound.label=Vault couldn't be found. Has it been moved?
|
||||
@@ -34,6 +43,9 @@ upgrade.button=Upgrade vault
|
||||
upgrade.version3dropBundleExtension.msg=This vault needs to be migrated to a newer format.\n"%1$s" will be renamed to "%2$s".\nPlease make sure synchronization has finished before proceeding.
|
||||
upgrade.version3dropBundleExtension.err.alreadyExists=Automatic migration failed.\n"%s" already exists.
|
||||
|
||||
upgrade.version3to4.msg=This vault needs to be migrated to a newer format.\nEncrypted folder names will be updated.\nPlease make sure synchronization has finished before proceeding.
|
||||
upgrade.version3to4.err.io=Migration failed due to an I/O Exception. See log file for details.
|
||||
|
||||
# unlock.fxml
|
||||
unlock.label.password=Password
|
||||
unlock.label.mountName=Drive name
|
||||
@@ -45,8 +57,7 @@ unlock.button.advancedOptions.show=More options
|
||||
unlock.button.advancedOptions.hide=Less options
|
||||
unlock.choicebox.winDriveLetter.auto=Assign automatically
|
||||
unlock.errorMessage.wrongPassword=Wrong password
|
||||
unlock.errorMessage.mountingFailed=Mounting failed. See logfile for details.
|
||||
unlock.errorMessage.unsupportedKeyLengthInstallJCE=Decryption failed. Please install Oracle JCE Unlimited Strength Policy.
|
||||
unlock.errorMessage.mountingFailed=Mounting failed. See log file for details.
|
||||
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware=Unsupported vault. This vault has been created with an older version of Cryptomator.
|
||||
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault=Unsupported vault. This vault has been created with a newer version of Cryptomator.
|
||||
unlock.messageLabel.startServerFailed=Starting WebDAV server failed.
|
||||
@@ -59,9 +70,6 @@ changePassword.label.downloadsPageLink=All Cryptomator versions
|
||||
changePassword.button.change=Change password
|
||||
changePassword.errorMessage.wrongPassword=Wrong password
|
||||
changePassword.errorMessage.decryptionFailed=Decryption failed
|
||||
changePassword.errorMessage.unsupportedKeyLengthInstallJCE=Decryption failed. Please install Oracle JCE Unlimited Strength Policy.
|
||||
changePassword.errorMessage.unsupportedVersion.vaultOlderThanSoftware=Unsupported vault. This vault has been created with an older version of Cryptomator.
|
||||
changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault=Unsupported vault. This vault has been created with a newer version of Cryptomator.
|
||||
changePassword.infoMessage.success=Password changed
|
||||
|
||||
# unlocked.fxml
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
# Copyright (c) 2016 The Cryptomator Contributors
|
||||
# This file is licensed under the terms of the MIT license.
|
||||
# See the LICENSE.txt file for more info.
|
||||
#
|
||||
# Contributos:
|
||||
# Jay
|
||||
# Sebastian Wiesendahl
|
||||
|
||||
app.name = Cryptomator
|
||||
# main.fxml
|
||||
main.emptyListInstructions = Click aquí para añadir una caja fuerte
|
||||
@@ -16,13 +8,13 @@ main.addDirectory.contextMenu.new = Crear una nueva caja fuerte
|
||||
main.addDirectory.contextMenu.open = Abrir una caja fuerte existente
|
||||
# welcome.fxml
|
||||
welcome.checkForUpdates.label.currentlyChecking = Chequando por actualizaciónes...
|
||||
welcome.newVersionMessage = Se puede bajar version %s. Este es %s.
|
||||
welcome.newVersionMessage = Se puede bajar version %1$s. Este es %2$s.
|
||||
# initialize.fxml
|
||||
initialize.label.password = Contraseña
|
||||
initialize.label.retypePassword = Reintroduzca contraseña
|
||||
initialize.button.ok = Crear caja fuerte
|
||||
initialize.messageLabel.alreadyInitialized = Caja fuerte ya está inicializado
|
||||
initialize.messageLabel.initializationFailed = No se pudo inicializar la caja fuerte. Ver archivo de registro para detalles.
|
||||
initialize.messageLabel.initializationFailed = No se pudo inicializar la caja fuerte. Ver archivo de registro para detalles.
|
||||
# notfound.fxml
|
||||
notfound.label = No se pudo encontrar la caja fuerte. Se movió a otro lugar?
|
||||
# upgrade.fxml
|
||||
@@ -41,7 +33,6 @@ unlock.button.advancedOptions.hide = Menos opciones
|
||||
unlock.choicebox.winDriveLetter.auto = Asignar automáticamente
|
||||
unlock.errorMessage.wrongPassword = Contraseña incorrecta
|
||||
unlock.errorMessage.mountingFailed = Monteo ha fallado. Ver archivo del registro para detalles.
|
||||
unlock.errorMessage.unsupportedKeyLengthInstallJCE = Decifración ha fallado. Por favor instala archivos de la Oracle JCE Unlimited Strength Policy.
|
||||
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Caja fuerte insupportado. Este caja se ha creado con una versión pasada de Cryptomator.
|
||||
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Caja fuerte insupportado. Este caja se ha creado con una versión nueva de Cryptomator.
|
||||
unlock.messageLabel.startServerFailed = Iniciación del servidor de WebDAV ha fallado.
|
||||
@@ -54,13 +45,10 @@ changePassword.label.downloadsPageLink = Todas las versiones de Cryptomator
|
||||
changePassword.button.change = Cambiar contraseña
|
||||
changePassword.errorMessage.wrongPassword = Contraseña incorrecta
|
||||
changePassword.errorMessage.decryptionFailed = Decifración ha fallado
|
||||
changePassword.errorMessage.unsupportedKeyLengthInstallJCE = Decifración ha fallado. Por favor instala Oracle JCE Unlimited Strength Policy.
|
||||
changePassword.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Caja fuerte insupportado. Este caja se ha creado con una versión pasada de Cryptomator.
|
||||
changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault = Caja fuerte insupportado. Este caja se ha creado con una versión nueva de Cryptomator.
|
||||
changePassword.infoMessage.success = Contraseña se ha cambiado
|
||||
# unlocked.fxml
|
||||
unlocked.button.lock = Encerrar caja fuerte
|
||||
unlocked.moreOptions.reveal = Revelar disco
|
||||
unlocked.moreOptions.reveal = Mostrar disco
|
||||
unlocked.moreOptions.copyUrl = Copiar URL de WebDAV
|
||||
unlocked.label.revealFailed = Comando ha fallado
|
||||
unlocked.label.unmountFailed = Expulsar el disco ha fallado
|
||||
@@ -83,5 +71,16 @@ settings.requiresRestartLabel = * Cryptomator se necesita reiniciar
|
||||
tray.menu.open = Abrir
|
||||
tray.menu.quit = Salir
|
||||
tray.infoMsg.title = Todavía en ejecución
|
||||
tray.infoMsg.msg = Cryptomator todavía esta en ejecución. Sale del icono del tray.
|
||||
tray.infoMsg.msg.osx = Cryptomator todavía esta en ejecución. Sale del icono de la barra del menú.
|
||||
tray.infoMsg.msg = Cryptomator todavía esta en ejecución. Para salir usa el icono del tray.
|
||||
tray.infoMsg.msg.osx = Cryptomator todavía esta en ejecución. Para salir usa el icono de la barra del menú.
|
||||
initialize.messageLabel.passwordStrength.0 = muy débil
|
||||
initialize.messageLabel.passwordStrength.1 = débil
|
||||
initialize.messageLabel.passwordStrength.2 = suficiente
|
||||
initialize.messageLabel.passwordStrength.3 = fuerte
|
||||
initialize.messageLabel.passwordStrength.4 = muy fuerte
|
||||
initialize.label.doNotForget = IMPORTANTE\: Si olivdes tú contraseña no hay ninguna manera de recuperar tus datos.
|
||||
main.directoryList.remove.confirmation.title = Borrar caja fuerte
|
||||
main.directoryList.remove.confirmation.header = ¿Quieres de verdad borrar este caja fuerte?
|
||||
main.directoryList.remove.confirmation.content = La caja fuerte solo se borra de la lista. Para eliminarla permanente por favor elimina los datos de tú sistema de archivos.
|
||||
upgrade.version3to4.msg = Este caja fuerte se debe migrar a un formato más reciente.\nLos nombres de las carpetas cifradas se actualizan.\nPor favor asegurarse de que la sincronización ha terminado antes de seguir.
|
||||
upgrade.version3to4.err.io = Migración ha fallado por causa de una excepción I/O. Para detalles revisa el archivo de registro.
|
||||
@@ -1,12 +1,3 @@
|
||||
# Copyright (c) 2016 The Cryptomator Contributors
|
||||
# This file is licensed under the terms of the MIT license.
|
||||
# See the LICENSE.txt file for more info.
|
||||
#
|
||||
# Contributors:
|
||||
# Jean-Noël Charon
|
||||
# Olivier Thomasson
|
||||
# vesparny
|
||||
|
||||
app.name = Cryptomator
|
||||
# main.fxml
|
||||
main.emptyListInstructions = Cliquez ici pour ajouter un coffre
|
||||
@@ -16,7 +7,7 @@ main.addDirectory.contextMenu.new = Créer un nouveau coffre
|
||||
main.addDirectory.contextMenu.open = Ouvrir un coffre existant
|
||||
# welcome.fxml
|
||||
welcome.checkForUpdates.label.currentlyChecking = Recherche de mise à jour...
|
||||
welcome.newVersionMessage = La version %s peut-être téléchargée. Il s'agit de %s.
|
||||
welcome.newVersionMessage = La version %1$s peut-être téléchargée. Il s'agit de %2$s.
|
||||
# initialize.fxml
|
||||
initialize.label.password = Mot de passe
|
||||
initialize.label.retypePassword = Confirmation
|
||||
@@ -41,7 +32,6 @@ unlock.button.advancedOptions.hide = Moins d'options
|
||||
unlock.choicebox.winDriveLetter.auto = Assigner automatiquement
|
||||
unlock.errorMessage.wrongPassword = Mot de passe incorrect
|
||||
unlock.errorMessage.mountingFailed = Echec du montage. Voir le fichier de log pour plus de détails.
|
||||
unlock.errorMessage.unsupportedKeyLengthInstallJCE = Echec du décryptage. Veuillez installer la Policy Oracle "JCE Unlimited Strength Policy".
|
||||
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Coffre non supporté. Ce coffre a été créé avec une ancienne version de Cryptomator.
|
||||
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Coffre non supporté. Ce coffre a été créé avec une version de Cryptomator plus récente.
|
||||
unlock.messageLabel.startServerFailed = Le serveur WebDAV n'a pas pu démarrer.
|
||||
@@ -52,25 +42,25 @@ changePassword.label.retypePassword = Vérification
|
||||
changePassword.label.downloadsPageLink = Toutes les versions de Cryptomator
|
||||
changePassword.button.change = Modification du mot de masse
|
||||
changePassword.errorMessage.wrongPassword = Mot de passe incorrect
|
||||
changePassword.errorMessage.decryptionFailed = Echec du décryptage
|
||||
changePassword.errorMessage.unsupportedKeyLengthInstallJCE = Echec du décryptage. Veuillez installer la Policy Oracle "JCE Unlimited Strength Policy".
|
||||
changePassword.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Coffre non supporté. Ce coffre a été créé avec une ancienne version de Cryptomator.
|
||||
changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault = Coffre non supporté. Ce coffre a été créé avec une version de Cryptomator plus récente.
|
||||
changePassword.infoMessage.success = Password changed
|
||||
# En français, on dit déchiffrement lorsque la clé est connue
|
||||
changePassword.errorMessage.decryptionFailed = Echec du déchiffrement
|
||||
changePassword.infoMessage.success = Mot de passe modifié
|
||||
# unlocked.fxml
|
||||
unlocked.button.lock = Verrouiller le coffre
|
||||
unlocked.moreOptions.reveal = Voir le lecteur
|
||||
unlocked.moreOptions.copyUrl = Copier l'URL WebDAV
|
||||
unlocked.label.revealFailed = Echec de la commande
|
||||
unlocked.label.unmountFailed = Echec de l'éjection du lecteur
|
||||
unlocked.label.statsEncrypted = crypté
|
||||
unlocked.label.statsDecrypted = décryptage
|
||||
# Crypter n'existe pas en français.
|
||||
unlocked.label.statsEncrypted = chiffré
|
||||
# Décryptage signifie récupérer les données en clair tout en ne connaissant pas la clé
|
||||
unlocked.label.statsDecrypted = déchiffré
|
||||
unlocked.ioGraph.yAxis.label = Débit (MiB/s)
|
||||
# mac_warnings.fxml
|
||||
macWarnings.windowTitle = Attention - Fichier corrompu dans %s
|
||||
macWarnings.message = Cryptomator a détecté des corruptions de données dans les fichiers suivants\:
|
||||
macWarnings.moreInformationButton = En savoir plus
|
||||
macWarnings.whitelistButton = Décrypter tout de même
|
||||
macWarnings.whitelistButton = Déchiffrer tout de même
|
||||
# settings.fxml
|
||||
settings.version.label = Version %s
|
||||
settings.checkForUpdates.label = Vérif. des mises à jour
|
||||
@@ -84,3 +74,14 @@ tray.menu.quit = Quitter
|
||||
tray.infoMsg.title = Toujours en fonctionnement
|
||||
tray.infoMsg.msg = Cryptomator est toujours en fonctionnement. Utiliser l'icône de la barre des tâches pour quitter.
|
||||
tray.infoMsg.msg.osx = Cryptomator est toujours en fonctionnement. Utilisez la barre de menu pour quitter.
|
||||
initialize.messageLabel.passwordStrength.0 = Très faible
|
||||
initialize.messageLabel.passwordStrength.1 = Faible
|
||||
initialize.messageLabel.passwordStrength.2 = Raisonnable
|
||||
initialize.messageLabel.passwordStrength.3 = Fort
|
||||
initialize.messageLabel.passwordStrength.4 = Très fort\n
|
||||
initialize.label.doNotForget = ATTENTION \: Si vous oubliez votre mot de passe, il n'y aura aucun moyen de récupérer vos données.
|
||||
main.directoryList.remove.confirmation.title = Retirer un coffre
|
||||
main.directoryList.remove.confirmation.header = Voulez-vous vraiment retirer ce coffre ?
|
||||
main.directoryList.remove.confirmation.content = Le coffre sera simplement retiré de la liste. Pour le supprimer complètement, supprimez les fichiers depuis votre système de fichiers.
|
||||
upgrade.version3to4.msg = Ce coffre doit être converti dans un nouveau format. Les noms de dossiers chiffrés seront mis à jour.\nMerci de vous assurer que la procédure de synchronisation est terminée avant de continuer.
|
||||
upgrade.version3to4.err.io = La migration a échoué à cause d'une erreur d'entrée/sortie. Référez-vous au fichier log pour plus de détails.
|
||||
@@ -1,20 +1,13 @@
|
||||
# Copyright (c) 2016 The Cryptomator Contributors
|
||||
# This file is licensed under the terms of the MIT license.
|
||||
# See the LICENSE.txt file for more info.
|
||||
#
|
||||
# Contributors:
|
||||
# Roland Burda
|
||||
|
||||
app.name = Cryptomator
|
||||
# main.fxml
|
||||
main.emptyListInstructions = Kattints ide egy széf létrehozásához
|
||||
main.directoryList.contextMenu.remove = Eltávolítás listából
|
||||
main.directoryList.contextMenu.changePassword = Jelszó megváltoztatása
|
||||
main.addDirectory.contextMenu.new = Új széf létrehozása
|
||||
main.addDirectory.contextMenu.open = Létez\u0151 széf megnyitása
|
||||
main.addDirectory.contextMenu.open = Létező széf megnyitása
|
||||
# welcome.fxml
|
||||
welcome.checkForUpdates.label.currentlyChecking = Frissítések keresése...
|
||||
welcome.newVersionMessage = Új verzió érhet\u0151 el\: %s. Jelenlegi verzió\: %s.
|
||||
welcome.newVersionMessage = Új verzió érhető el\: %1$s. Jelenlegi verzió\: %2$s.
|
||||
# initialize.fxml
|
||||
initialize.label.password = Jelszó
|
||||
initialize.label.retypePassword = Jelszó ismét
|
||||
@@ -25,12 +18,12 @@ initialize.messageLabel.initializationFailed = Nem sikerült megnyitni a széfet
|
||||
notfound.label = Széf nem található. Lehetséges, hogy áthelyezésre került?
|
||||
# upgrade.fxml
|
||||
upgrade.button = Széf frissítése
|
||||
upgrade.version3dropBundleExtension.msg = A széf új verzióra történ\u0151 migrációja szükséges. "%1$s" a következ\u0151re lesz átnevezve\: "%2$s". Kérlek gy\u0151z\u0151dj meg a szinkronizáció befejeztér\u0151l, miel\u0151tt más m\u0171veletet végeznél.
|
||||
upgrade.version3dropBundleExtension.msg = A széf új verzióra történő migrációja szükséges. "%1$s" a következőre lesz átnevezve\: "%2$s". Kérlek győződj meg a szinkronizáció befejeztéről, mielőtt más műveletet végeznél.
|
||||
upgrade.version3dropBundleExtension.err.alreadyExists = Automatikus migráció meghíusúlt. "%s" már létezik.
|
||||
# unlock.fxml
|
||||
unlock.label.password = Jelszó
|
||||
unlock.label.mountName = Meghajtó neve
|
||||
unlock.label.winDriveLetter = Meghajtó bet\u0171jele
|
||||
unlock.label.winDriveLetter = Meghajtó betűjele
|
||||
unlock.label.downloadsPageLink = Összes Cryptomator verzió
|
||||
unlock.label.advancedHeading = Haladó beállítások
|
||||
unlock.button.unlock = Széf feloldása
|
||||
@@ -39,7 +32,6 @@ unlock.button.advancedOptions.hide = Alapbeállítások
|
||||
unlock.choicebox.winDriveLetter.auto = Automatikus hozzárendelés
|
||||
unlock.errorMessage.wrongPassword = Hibás jelszó
|
||||
unlock.errorMessage.mountingFailed = Meghajtó felcsatolása sikertelen. További információk a naplófájlban.
|
||||
unlock.errorMessage.unsupportedKeyLengthInstallJCE = A titkosítás feloldása sikertelen. Kérlek telepítsd a "Oracle JCE Unlimited Strength Policy Files"-t.
|
||||
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Nem támogatott széf. Ez a széf a Cryptomator egy korábbi verziójával került létrehozásra.
|
||||
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Nem támogatott széf. Ez a széf a Cryptomator egy újabb verziójával került létrehozásra.
|
||||
unlock.messageLabel.startServerFailed = WebDAV szerver indítása sikertelen.
|
||||
@@ -51,9 +43,6 @@ changePassword.label.downloadsPageLink = Összes Cryptomator verzió
|
||||
changePassword.button.change = Jelszó megváltoztatása
|
||||
changePassword.errorMessage.wrongPassword = Hibás jelszó
|
||||
changePassword.errorMessage.decryptionFailed = A titkosítás feloldása meghíusúlt
|
||||
changePassword.errorMessage.unsupportedKeyLengthInstallJCE = A titkosítás feloldása sikertelen. Kérlek telepítsd a "Oracle JCE Unlimited Strength Policy"-t.
|
||||
changePassword.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Nem támogatott széf. Ez a széf a Cryptomator egy korábbi verziójával került létrehozásra.
|
||||
changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault = Nem támogatott széf. Ez a széf a Cryptomator egy újabb verziójával került létrehozásra.
|
||||
changePassword.infoMessage.success = Jelszó megváltoztatva
|
||||
# unlocked.fxml
|
||||
unlocked.button.lock = Széf lezárása
|
||||
@@ -65,8 +54,8 @@ unlocked.label.statsEncrypted = titkosított
|
||||
unlocked.label.statsDecrypted = titkosítás feloldva
|
||||
unlocked.ioGraph.yAxis.label = Teljesítmény (MiB/s)
|
||||
# mac_warnings.fxml
|
||||
macWarnings.windowTitle = Veszély - Korrupt fájl a következ\u0151ben\: %s
|
||||
macWarnings.message = Cryptomator potenciálisan rosszindulatú hibákat fedezett fel a következ\u0151 fájlokban\:
|
||||
macWarnings.windowTitle = Veszély - Korrupt fájl a következőben\: %s
|
||||
macWarnings.message = Cryptomator potenciálisan rosszindulatú hibákat fedezett fel a következő fájlokban\:
|
||||
macWarnings.moreInformationButton = Tudj meg többet
|
||||
macWarnings.whitelistButton = Kiválasztottak titkosításának feloldása mindenképp
|
||||
# settings.fxml
|
||||
@@ -79,6 +68,15 @@ settings.requiresRestartLabel = * Cryptomator újraindítása szükséges
|
||||
# tray icon
|
||||
tray.menu.open = Megnyit
|
||||
tray.menu.quit = Kilépés
|
||||
tray.infoMsg.title = M\u0171velet folyamatban
|
||||
tray.infoMsg.title = Művelet folyamatban
|
||||
tray.infoMsg.msg = Cryptomator még fut. A tálcán található ikon segítségével bezárhatod.
|
||||
tray.infoMsg.msg.osx = Cryptomator még fut. A menüsávban található ikon segítségével bezárhatod.
|
||||
initialize.messageLabel.passwordStrength.0 = Nagyon gyenge
|
||||
initialize.messageLabel.passwordStrength.1 = Gyenge
|
||||
initialize.messageLabel.passwordStrength.2 = Megfelelő
|
||||
initialize.messageLabel.passwordStrength.3 = Erős
|
||||
initialize.messageLabel.passwordStrength.4 = Nagyon erős
|
||||
initialize.label.doNotForget = FONTOS\: Ha elfelejted a jelszavadat, akkor nincs lehetőség az adataid visszaállítására.
|
||||
main.directoryList.remove.confirmation.title = Széf eltávolítása
|
||||
main.directoryList.remove.confirmation.header = Tényleg törölni akarod ezt a széfet?
|
||||
main.directoryList.remove.confirmation.content = A széf csak a listából lesz eltávolítva. Végleges törléshez kérlek töröld a merevlemezen tárolt fájlokat.
|
||||
@@ -1,10 +1,3 @@
|
||||
# Copyright (c) 2016 The Cryptomator Contributors
|
||||
# This file is licensed under the terms of the MIT license.
|
||||
# See the LICENSE.txt file for more info.
|
||||
#
|
||||
# Contributors:
|
||||
# vesparny
|
||||
|
||||
app.name = Cryptomator
|
||||
# main.fxml
|
||||
main.emptyListInstructions = Clicca qui per aggiungere un vault
|
||||
@@ -14,7 +7,7 @@ main.addDirectory.contextMenu.new = Crea un nuovo vault
|
||||
main.addDirectory.contextMenu.open = Apri un vault
|
||||
# welcome.fxml
|
||||
welcome.checkForUpdates.label.currentlyChecking = Verifica aggiornamenti...
|
||||
welcome.newVersionMessage = La versione %s può essere scaricata. Questa è %s
|
||||
welcome.newVersionMessage = La versione %1$s può essere scaricata. Questa è %2$s
|
||||
# initialize.fxml
|
||||
initialize.label.password = Password
|
||||
initialize.label.retypePassword = Conferma password
|
||||
@@ -39,7 +32,6 @@ unlock.button.advancedOptions.hide = Meno opzioni
|
||||
unlock.choicebox.winDriveLetter.auto = Assegna automaticamente
|
||||
unlock.errorMessage.wrongPassword = Password errata
|
||||
unlock.errorMessage.mountingFailed = Montaggio fallito. Controlla il file di log per dettagli.
|
||||
unlock.errorMessage.unsupportedKeyLengthInstallJCE = Decriptaggio fallito.
|
||||
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Vault non supportato. Questo vault è stato creato con una versione di Cryptomator più vecchia.
|
||||
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Vault non supportato. Questo vault è stato creato con una versione di Cryptomator più recente.
|
||||
unlock.messageLabel.startServerFailed = Avvio del server WebDAV fallito
|
||||
@@ -51,19 +43,16 @@ changePassword.label.downloadsPageLink = Tutte le versioni di Cryptomator
|
||||
changePassword.button.change = Cambia la password
|
||||
changePassword.errorMessage.wrongPassword = Password errata
|
||||
changePassword.errorMessage.decryptionFailed = Decriptaggio fallito
|
||||
changePassword.errorMessage.unsupportedKeyLengthInstallJCE = Decriptaggio fallito. Per favore installa Oracle JCE Unlimited Strength Policy
|
||||
changePassword.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Vault non supportato. Questo vault è stato creato con una versione di Cryptomator più recente.
|
||||
changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault = Vault non supportato. Questo vault è stato creato con una versione di Cryptomator più vecchia.
|
||||
changePassword.infoMessage.success = Password cambiata
|
||||
# unlocked.fxml
|
||||
unlocked.button.lock = Blocca vault
|
||||
unlocked.button.lock = Blocca vault
|
||||
unlocked.moreOptions.reveal = Apri il disco
|
||||
unlocked.moreOptions.copyUrl = Copia url WebDAV
|
||||
unlocked.label.revealFailed = Comando fallito
|
||||
unlocked.label.unmountFailed = Espulsione disco fallita
|
||||
unlocked.label.statsEncrypted = criptato
|
||||
unlocked.label.statsDecrypted = decriptato
|
||||
unlocked.ioGraph.yAxis.label = Volume dati (MiB/s)
|
||||
unlocked.ioGraph.yAxis.label = Volume dati (MiB/s)
|
||||
# mac_warnings.fxml
|
||||
macWarnings.windowTitle = Pericolo - File corroto in %s
|
||||
macWarnings.message = Cryptomator ha individuato potenziali pericolose corruzioni nei seguenti file\:
|
||||
@@ -80,5 +69,14 @@ settings.requiresRestartLabel = * Cryptomator deve essere riavviato
|
||||
tray.menu.open = Apri
|
||||
tray.menu.quit = Chiudi
|
||||
tray.infoMsg.title = Ancora in esecuzione
|
||||
tray.infoMsg.msg = Cryptomator è ancora in esecuzione. Chiudilo utilizzando l'icona nel menù di stato.
|
||||
tray.infoMsg.msg = Cryptomator è ancora in esecuzione. Chiudilo utilizzando l'icona nel menù di stato.
|
||||
tray.infoMsg.msg.osx = Cryptomator è ancora in esecuzione. Chiudilo utilizzando l'icona nella barra del menù.
|
||||
initialize.messageLabel.passwordStrength.0 = Molto debole
|
||||
initialize.messageLabel.passwordStrength.1 = Debole
|
||||
initialize.messageLabel.passwordStrength.2 = Buona
|
||||
initialize.messageLabel.passwordStrength.3 = Sicura
|
||||
initialize.messageLabel.passwordStrength.4 = Molto sicura
|
||||
initialize.label.doNotForget = IMPORTANTE\: Se dimentichi la password, non c'è modo di recuperare i tuoi dati.
|
||||
main.directoryList.remove.confirmation.title = Rimuovi vault
|
||||
main.directoryList.remove.confirmation.header = Vuoi davvero rimuovere questo vault?
|
||||
main.directoryList.remove.confirmation.content = Il vault sarà rimosso solo dalla lista. Per eliminarlo definitivamente, elimina per favore i file dal tuo hard disk.
|
||||