Compare commits

...

38 Commits

Author SHA1 Message Date
Sebastian Stenzel
d9ba4935b6 restored compatibility with vaults created on the iOS app 2016-03-09 09:01:26 +01:00
Sebastian Stenzel
b6ee29789e linking to /faq instead of /help.html (new website) [ci skip] 2016-03-09 00:38:20 +01:00
Markus Kreusch
5ee82271f5 Improved wording [ci skip] 2016-03-08 16:16:59 +01:00
Markus Kreusch
2eb4d87dd1 fixes #160 2016-03-08 16:14:47 +01:00
Sebastian Stenzel
d0afeab74b UI refinements, fixes #166 2016-03-08 15:31:10 +01:00
Sebastian Stenzel
cc74c2c05b fixes #165 again 2016-03-07 17:37:27 +01:00
Sebastian Stenzel
8865cf0e4b Revert "fixes #165"
This reverts commit 65550ce70f.
2016-03-07 17:30:15 +01:00
Markus Kreusch
65550ce70f fixes #165 2016-03-07 17:25:29 +01:00
Markus Kreusch
78300f8bf1 Localization of stats labels 2016-03-07 14:56:34 +01:00
Markus Kreusch
32c65a7dda Skipping test execution for coverity builds 2016-03-07 14:03:24 +01:00
Markus Kreusch
6d31ed7ea4 fixes #163
* coverity issue 72253
2016-03-07 13:55:21 +01:00
Markus Kreusch
c3e5d3f38e fixes #162 2016-03-07 13:13:45 +01:00
Markus Kreusch
e3900231aa Added all FileSystems to invariants tests 2016-03-07 11:04:36 +01:00
Sebastian Stenzel
06f13c57d4 Added webdav url as "string" to the clipboard - additionally to "url" type. This should make it possible to paste the copied address nearly everywhere. #73 [ci skip] 2016-03-05 22:06:12 +01:00
Sebastian Stenzel
fc1a5be85f fixed "encrypt anyway" button 2016-03-05 16:02:05 +01:00
Sebastian Stenzel
a30b310c04 close underlying file, if exception in constructor of CryptoReadableFile or CryptoWritableFile 2016-03-05 14:49:46 +01:00
Sebastian Stenzel
956dd855f9 WebDAV: return null, if file size could not be determined e.g. due to invalid file headers 2016-03-05 14:33:11 +01:00
Sebastian Stenzel
67ba7cac40 Vault doesn't need to be Serializable in order for ObjectMapper to work properly 2016-03-04 21:27:46 +01:00
Sebastian Stenzel
9117b6bc0e Coverity 72994 2016-03-04 17:56:02 +01:00
Sebastian Stenzel
bae826be28 Coverity 72980 2016-03-04 17:53:55 +01:00
Sebastian Stenzel
d845e8d97a Coverity 72979 2016-03-04 17:50:07 +01:00
Sebastian Stenzel
b37b2e4fb7 Coverity 72941 2016-03-04 17:48:57 +01:00
Sebastian Stenzel
69f6a9927d Coverity 72975, 72976, 72977 2016-03-04 17:48:07 +01:00
Sebastian Stenzel
addc9533eb Coverity 72988 2016-03-04 17:46:41 +01:00
Sebastian Stenzel
8b717993ed Coverity 72944 2016-03-04 17:45:33 +01:00
Sebastian Stenzel
f70d486462 Coverity 72964 2016-03-04 17:44:12 +01:00
Sebastian Stenzel
293ac0ea3c delete empty directories inside ./d/ 2016-03-04 16:51:10 +01:00
Sebastian Stenzel
e99a615b09 closing channel, if registration failed. Coverity issue 72309 2016-03-04 01:49:43 +01:00
Sebastian Stenzel
6da3fde864 work with number of bytes returned by ReadableFile.read(), Coverity issues 72259 and 72261 2016-03-04 01:39:50 +01:00
Sebastian Stenzel
3a725e4a16 fixed equals methods, Coverity issues 72280, 72281, 72283, 72284 2016-03-04 01:34:38 +01:00
Sebastian Stenzel
e3256a747f fixes Coverity issue 72287 2016-03-04 01:31:26 +01:00
Sebastian Stenzel
adc20ea2f2 code simplification, added further assertions for Coverity issue 72293 2016-03-04 01:29:34 +01:00
Sebastian Stenzel
997f841662 Only compare versions, if parsing was successful, see Coverity issue 72294 2016-03-04 01:23:48 +01:00
Sebastian Stenzel
e57b60f04e value is known to be null anyway 2016-03-04 01:23:16 +01:00
Sebastian Stenzel
d5b4fb4fe9 json parsing exception handling, see Coverity issues 72297, 72296, 72295 2016-03-04 01:20:38 +01:00
Sebastian Stenzel
edf92adfec thread safety, see Coverity issues 72313 and 72314 2016-03-04 01:05:24 +01:00
Sebastian Stenzel
718bacafa6 added coverity to travis configuration 2016-03-03 21:10:55 +01:00
Sebastian Stenzel
7122bdf199 changed version to 0.12.0-SNAPSHOT [ci skip] 2016-03-03 20:42:19 +01:00
55 changed files with 462 additions and 202 deletions

View File

@@ -5,7 +5,8 @@ jdk:
env:
global:
- secure: "Lgj042RD0X3rB8VZVZLWP1GetLhjd3PqI5JbJMlzgHJpDI6RkFIBLN9SWAGmkLPCehIp2zA5tu9+UVy0NNMxm9xz6SyjMCaxS28/fnYEXaNmwwDSF6O6gLUbdxyzoYIFPYOPmFxpzhebqnNIsxaM29oZpgRgUGqosCczQxiB+Ng="
- secure: "Lgj042RD0X3rB8VZVZLWP1GetLhjd3PqI5JbJMlzgHJpDI6RkFIBLN9SWAGmkLPCehIp2zA5tu9+UVy0NNMxm9xz6SyjMCaxS28/fnYEXaNmwwDSF6O6gLUbdxyzoYIFPYOPmFxpzhebqnNIsxaM29oZpgRgUGqosCczQxiB+Ng=" #coveralls
- secure: "IfYURwZaDWuBDvyn47n0k1Zod/IQw1FF+CS5nnV08Q+NfC3vGGJMwV8m59XnbfwnWGxwvCaAbk4qP6s6+ijgZNKkvgfFMo3rfTok5zt43bIqgaFOANYV+OC/1c59gYD6ZUxhW5iNgMgU3qdsRtJuwSmfkVv/jKyLGfAbS4kN8BA=" #coverity
before_install: "curl -L --cookie 'oraclelicense=accept-securebackup-cookie;' http://download.oracle.com/otn-pub/java/jce/8/jce_policy-8.zip -o /tmp/policy.zip && sudo unzip -j -o /tmp/policy.zip *.jar -d `jdk_switcher home oraclejdk8`/jre/lib/security && rm /tmp/policy.zip"
@@ -28,6 +29,14 @@ notifications:
before_deploy: mvn -fmain/pom.xml -Puber-jar clean package -DskipTests
addons:
coverity_scan:
project:
name: "cryptomator/cryptomator"
notification_email: sebastian.stenzel@cryptomator.org
build_command: "mvn -fmain/pom.xml clean test -DskipTests"
branch_pattern: coverity_scan
deploy:
provider: releases
prerelease: true

View File

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

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>0.11.0</version>
<version>1.0.0</version>
</parent>
<artifactId>commons</artifactId>
<name>Cryptomator common</name>

View File

@@ -9,7 +9,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>0.11.0</version>
<version>1.0.0</version>
</parent>
<artifactId>filesystem-api</artifactId>
<name>Cryptomator filesystem: API</name>

View File

@@ -12,7 +12,7 @@ import static java.lang.String.format;
import java.util.function.Consumer;
public class FileSystemVisitor {
public class FolderVisitor {
private final Consumer<Folder> beforeFolderVisitor;
private final Consumer<Folder> afterFolderVisitor;
@@ -20,7 +20,7 @@ public class FileSystemVisitor {
private final Consumer<Node> nodeVisitor;
private final int maxDepth;
public FileSystemVisitor(FileSystemVisitorBuilder builder) {
public FolderVisitor(FolderVisitorBuilder builder) {
this.beforeFolderVisitor = builder.beforeFolderVisitor;
this.afterFolderVisitor = builder.afterFolderVisitor;
this.fileVisitor = builder.fileVisitor;
@@ -28,19 +28,19 @@ public class FileSystemVisitor {
this.maxDepth = builder.maxDepth;
}
public static FileSystemVisitorBuilder fileSystemVisitor() {
return new FileSystemVisitorBuilder();
public static FolderVisitorBuilder folderVisitor() {
return new FolderVisitorBuilder();
}
public FileSystemVisitor visit(Folder folder) {
public FolderVisitor visit(Folder folder) {
return visit(folder, 0);
}
public FileSystemVisitor visit(File file) {
public FolderVisitor visit(File file) {
return visit(file, 0);
}
private FileSystemVisitor visit(Folder folder, int depth) {
private FolderVisitor visit(Folder folder, int depth) {
beforeFolderVisitor.accept(folder);
nodeVisitor.accept(folder);
final int childDepth = depth + 1;
@@ -52,13 +52,13 @@ public class FileSystemVisitor {
return this;
}
private FileSystemVisitor visit(File file, int depth) {
private FolderVisitor visit(File file, int depth) {
nodeVisitor.accept(file);
fileVisitor.accept(file);
return this;
}
public static class FileSystemVisitorBuilder {
public static class FolderVisitorBuilder {
private Consumer<Folder> beforeFolderVisitor = noOp();
private Consumer<Folder> afterFolderVisitor = noOp();
@@ -66,10 +66,10 @@ public class FileSystemVisitor {
private Consumer<Node> nodeVisitor = noOp();
private int maxDepth = Integer.MAX_VALUE;
private FileSystemVisitorBuilder() {
private FolderVisitorBuilder() {
}
public FileSystemVisitorBuilder beforeFolder(Consumer<Folder> beforeFolderVisitor) {
public FolderVisitorBuilder beforeFolder(Consumer<Folder> beforeFolderVisitor) {
if (beforeFolderVisitor == null) {
throw new IllegalArgumentException("Vistior may not be null");
}
@@ -77,7 +77,7 @@ public class FileSystemVisitor {
return this;
}
public FileSystemVisitorBuilder afterFolder(Consumer<Folder> afterFolderVisitor) {
public FolderVisitorBuilder afterFolder(Consumer<Folder> afterFolderVisitor) {
if (afterFolderVisitor == null) {
throw new IllegalArgumentException("Vistior may not be null");
}
@@ -85,7 +85,7 @@ public class FileSystemVisitor {
return this;
}
public FileSystemVisitorBuilder forEachFile(Consumer<File> fileVisitor) {
public FolderVisitorBuilder forEachFile(Consumer<File> fileVisitor) {
if (fileVisitor == null) {
throw new IllegalArgumentException("Vistior may not be null");
}
@@ -93,7 +93,7 @@ public class FileSystemVisitor {
return this;
}
public FileSystemVisitorBuilder forEachNode(Consumer<Node> nodeVisitor) {
public FolderVisitorBuilder forEachNode(Consumer<Node> nodeVisitor) {
if (nodeVisitor == null) {
throw new IllegalArgumentException("Vistior may not be null");
}
@@ -101,7 +101,7 @@ public class FileSystemVisitor {
return this;
}
public FileSystemVisitorBuilder withMaxDepth(int maxDepth) {
public FolderVisitorBuilder withMaxDepth(int maxDepth) {
if (maxDepth < 0) {
throw new IllegalArgumentException(format("maxDepth must not be smaller 0 but was %d", maxDepth));
}
@@ -109,16 +109,16 @@ public class FileSystemVisitor {
return this;
}
public FileSystemVisitor visit(Folder folder) {
public FolderVisitor visit(Folder folder) {
return build().visit(folder);
}
public FileSystemVisitor visit(File file) {
public FolderVisitor visit(File file) {
return build().visit(file);
}
public FileSystemVisitor build() {
return new FileSystemVisitor(this);
public FolderVisitor build() {
return new FolderVisitor(this);
}
private static <T> Consumer<T> noOp() {

View File

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

View File

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

View File

@@ -13,7 +13,6 @@ import static org.cryptomator.crypto.engine.impl.Constants.CURRENT_VAULT_VERSION
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
@@ -25,7 +24,6 @@ import javax.crypto.spec.SecretKeySpec;
import javax.security.auth.DestroyFailedException;
import javax.security.auth.Destroyable;
import org.apache.commons.lang3.ArrayUtils;
import org.cryptomator.common.LazyInitializer;
import org.cryptomator.crypto.engine.Cryptor;
import org.cryptomator.crypto.engine.FileContentCryptor;
@@ -35,6 +33,7 @@ import org.cryptomator.crypto.engine.UnsupportedVaultFormatException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
class CryptorImpl implements Cryptor {
@@ -99,12 +98,16 @@ class CryptorImpl implements Cryptor {
try {
final ObjectMapper om = new ObjectMapper();
keyFile = om.readValue(masterkeyFileContents, KeyFile.class);
if (keyFile == null) {
throw new InvalidFormatException("Could not read masterkey file", null, KeyFile.class);
}
} catch (IOException e) {
throw new IllegalArgumentException("Unable to parse masterkeyFileContents", e);
}
assert keyFile != null;
// check version
if (keyFile.getVersion() != CURRENT_VAULT_VERSION || ArrayUtils.isEmpty(keyFile.getVersionMac())) {
if (!CURRENT_VAULT_VERSION.equals(keyFile.getVersion())) {
throw new UnsupportedVaultFormatException(keyFile.getVersion(), CURRENT_VAULT_VERSION);
}
@@ -112,12 +115,13 @@ class CryptorImpl implements Cryptor {
try {
final SecretKey kek = new SecretKeySpec(kekBytes, ENCRYPTION_ALG);
this.macKey = AesKeyWrap.unwrap(kek, keyFile.getMacMasterKey(), MAC_ALG);
final Mac mac = new ThreadLocalMac(macKey, MAC_ALG).get();
final byte[] versionMac = mac.doFinal(ByteBuffer.allocate(Integer.BYTES).putInt(CURRENT_VAULT_VERSION).array());
if (!MessageDigest.isEqual(versionMac, keyFile.getVersionMac())) {
destroyQuietly(macKey);
throw new UnsupportedVaultFormatException(Integer.MAX_VALUE, CURRENT_VAULT_VERSION);
}
// future use (as soon as we need to prevent downgrade attacks):
// final Mac mac = new ThreadLocalMac(macKey, MAC_ALG).get();
// final byte[] versionMac = mac.doFinal(ByteBuffer.allocate(Integer.BYTES).putInt(CURRENT_VAULT_VERSION).array());
// if (!MessageDigest.isEqual(versionMac, keyFile.getVersionMac())) {
// destroyQuietly(macKey);
// throw new UnsupportedVaultFormatException(Integer.MAX_VALUE, CURRENT_VAULT_VERSION);
// }
this.encryptionKey = AesKeyWrap.unwrap(kek, keyFile.getEncryptionMasterKey(), ENCRYPTION_ALG);
} catch (InvalidKeyException e) {
throw new InvalidPassphraseException();

View File

@@ -111,7 +111,8 @@ class BlockAlignedWritableFile implements WritableFile {
currentBlockBuffer.clear();
try (ReadableFile r = openReadable.get()) {
r.position(physicalPosition);
r.read(currentBlockBuffer);
int numRead = r.read(currentBlockBuffer);
assert numRead == currentBlockBuffer.position();
}
int advance = (int) (logicalPosition - physicalPosition);
currentBlockBuffer.position(advance);

View File

@@ -47,10 +47,10 @@ class CiphertextReader implements Callable<Void> {
int bytesRead = -1;
do {
ByteBuffer ciphertext = ByteBuffer.allocate(READ_BUFFER_SIZE);
file.read(ciphertext);
ciphertext.flip();
bytesRead = ciphertext.remaining();
bytesRead = file.read(ciphertext);
if (bytesRead > 0) {
ciphertext.flip();
assert bytesRead == ciphertext.remaining();
decryptor.append(ciphertext);
}
} while (bytesRead > 0);

View File

@@ -35,7 +35,17 @@ class CryptoFile extends CryptoNode implements File {
@Override
public ReadableFile openReadable() {
boolean authenticate = !fileSystem().delegate().shouldSkipAuthentication(toString());
return new CryptoReadableFile(cryptor.getFileContentCryptor(), forceGetPhysicalFile().openReadable(), authenticate, this::reportAuthError);
ReadableFile physicalReadable = forceGetPhysicalFile().openReadable();
boolean success = false;
try {
final ReadableFile result = new CryptoReadableFile(cryptor.getFileContentCryptor(), physicalReadable, authenticate, this::reportAuthError);
success = true;
return result;
} finally {
if (!success) {
physicalReadable.close();
}
}
}
private void reportAuthError() {
@@ -47,7 +57,17 @@ class CryptoFile extends CryptoNode implements File {
if (parent.folder(name).exists()) {
throw new UncheckedIOException(new FileAlreadyExistsException(toString()));
}
return new CryptoWritableFile(cryptor.getFileContentCryptor(), forceGetPhysicalFile().openWritable());
WritableFile physicalWrtiable = forceGetPhysicalFile().openWritable();
boolean success = false;
try {
final WritableFile result = new CryptoWritableFile(cryptor.getFileContentCryptor(), physicalWrtiable);
success = true;
return result;
} finally {
if (!success) {
physicalWrtiable.close();
}
}
}
@Override

View File

@@ -160,8 +160,8 @@ class CryptoFolder extends CryptoNode implements Folder {
assert target.parent().isPresent() : "Target can not be root, thus has a parent";
// prepare target:
target.parent().get().create();
target.delete();
target.parent().get().create();
// perform the actual move:
final File dirFile = forceGetPhysicalFile();
@@ -186,7 +186,12 @@ class CryptoFolder extends CryptoNode implements Folder {
return;
}
Deleter.deleteContent(this);
forceGetPhysicalFolder().delete();
Folder physicalFolder = forceGetPhysicalFolder();
physicalFolder.delete();
Folder physicalFolderParent = physicalFolder.parent().get();
if (physicalFolderParent.folders().count() == 0) {
physicalFolderParent.delete();
}
forceGetPhysicalFile().delete();
invalidateDirectoryIdsRecursively();
}

View File

@@ -104,8 +104,8 @@ abstract class CryptoNode implements Node {
if (obj instanceof CryptoNode) {
CryptoNode other = (CryptoNode) obj;
return this.getClass() == other.getClass() //
&& (this.parent == null && other.parent == null || this.parent.equals(other.parent)) //
&& (this.name == null && other.name == null || this.name.equals(other.name));
&& (this.parent == null && other.parent == null || this.parent != null && this.parent.equals(other.parent)) //
&& (this.name == null && other.name == null || this.name != null && this.name.equals(other.name));
} else {
return false;
}

View File

@@ -43,7 +43,10 @@ class CryptoReadableFile implements ReadableFile {
this.authenticate = authenticate;
this.onAuthError = onAuthError;
file.position(0);
file.read(header);
int headerBytesRead = file.read(header);
if (headerBytesRead != header.capacity()) {
throw new IllegalArgumentException("File too short to contain a header.");
}
header.flip();
this.position(0);
}

View File

@@ -14,6 +14,7 @@ import org.cryptomator.crypto.engine.Cryptor;
import org.cryptomator.crypto.engine.InvalidPassphraseException;
import org.cryptomator.crypto.engine.UnsupportedVaultFormatException;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
public class CryptorImplTest {
@@ -48,6 +49,7 @@ public class CryptorImplTest {
cryptor.readKeysFromMasterkeyFile(testMasterKey.getBytes(), "asd");
}
@Ignore
@Test(expected = UnsupportedVaultFormatException.class)
public void testMasterkeyDecryptionWithMissingVersionMac() throws IOException {
final String testMasterKey = "{\"version\":3,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," //
@@ -57,6 +59,7 @@ public class CryptorImplTest {
cryptor.readKeysFromMasterkeyFile(testMasterKey.getBytes(), "asd");
}
@Ignore
@Test(expected = UnsupportedVaultFormatException.class)
public void testMasterkeyDecryptionWithWrongVersionMac() throws IOException {
final String testMasterKey = "{\"version\":3,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," //

View File

@@ -8,14 +8,15 @@
*******************************************************************************/
package org.cryptomator.filesystem.crypto;
import static org.cryptomator.filesystem.FileSystemVisitor.fileSystemVisitor;
import static org.hamcrest.Matchers.both;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.lessThanOrEqualTo;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.time.Instant;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import org.cryptomator.crypto.engine.Cryptor;
import org.cryptomator.crypto.engine.NoCryptor;
@@ -37,7 +38,6 @@ public class CryptoFileSystemTest {
final FileSystem physicalFs = new InMemoryFileSystem();
final Folder physicalDataRoot = physicalFs.folder("d");
final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor, Mockito.mock(CryptoFileSystemDelegate.class), "foo");
fs.create();
// add another encrypted folder:
final Folder fooFolder = fs.folder("foo");
@@ -47,8 +47,24 @@ public class CryptoFileSystemTest {
fooBarFolder.create();
Assert.assertTrue(fooFolder.exists());
Assert.assertTrue(fooBarFolder.exists());
Assert.assertEquals(3, countDataFolders(physicalDataRoot)); // parent +
// foo + bar
Assert.assertEquals(3, countDataFolders(physicalDataRoot)); // parent + foo + bar
}
@Test(timeout = 1000)
public void testDirectoryDeletion() throws UncheckedIOException, IOException {
// mock stuff and prepare crypto FS:
final Cryptor cryptor = new NoCryptor();
final FileSystem physicalFs = new InMemoryFileSystem();
final Folder physicalDataRoot = physicalFs.folder("d");
final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor, Mockito.mock(CryptoFileSystemDelegate.class), "foo");
// create and delete folders:
fs.folder("foo").folder("bar").folder("baz").create();
Assert.assertEquals(4, countDataFolders(physicalDataRoot)); // root + foo + bar + baz
Assert.assertThat(physicalDataRoot.folders().count(), both(greaterThanOrEqualTo(1l)).and(lessThanOrEqualTo(4l))); // parent folders of the 4 folders
fs.folder("foo").delete();
Assert.assertEquals(1, countDataFolders(physicalDataRoot)); // just root
Assert.assertEquals(1, physicalDataRoot.folders().count()); // just the parent of root
}
@Test(timeout = 2000)
@@ -204,22 +220,10 @@ public class CryptoFileSystemTest {
}
/**
* @return number of folders on second level inside the given dataRoot
* folder.
* @return number of folders on second level inside the given dataRoot folder.
*/
private static int countDataFolders(Folder dataRoot) {
final AtomicInteger num = new AtomicInteger();
fileSystemVisitor() //
.afterFolder(folder -> {
final Folder parent = folder.parent().get();
final Folder parentOfParent = parent.parent().orElse(null);
if (parentOfParent != null && parentOfParent.equals(dataRoot)) {
num.incrementAndGet();
}
}) //
.withMaxDepth(2) //
.visit(dataRoot);
return num.get();
return (int) dataRoot.folders().flatMap(Folder::folders).count();
}
}

View File

@@ -12,7 +12,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>0.11.0</version>
<version>1.0.0</version>
</parent>
<artifactId>filesystem-inmemory</artifactId>
<name>Cryptomator filesystem: In-memory mock</name>

View File

@@ -13,6 +13,7 @@ import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.nio.file.FileAlreadyExistsException;
import java.time.Instant;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
@@ -30,11 +31,16 @@ class InMemoryFile extends InMemoryNode implements File {
static final double GROWTH_RATE = 1.4;
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private volatile ByteBuffer content = ByteBuffer.allocate(INITIAL_SIZE);
private final AtomicReference<ByteBuffer> content = new AtomicReference<>(createNewEmptyByteBuffer());
public InMemoryFile(InMemoryFolder parent, String name, Instant lastModified, Instant creationTime) {
super(parent, name, lastModified, creationTime);
content.flip();
}
static ByteBuffer createNewEmptyByteBuffer() {
final ByteBuffer buf = ByteBuffer.allocate(INITIAL_SIZE);
buf.flip();
return buf;
}
@Override
@@ -47,9 +53,9 @@ class InMemoryFile extends InMemoryNode implements File {
}
private void internalMoveTo(InMemoryFile destination) {
this.content.rewind();
this.content.get().rewind();
destination.create();
destination.content = this.content;
destination.content.set(this.content.getAndSet(createNewEmptyByteBuffer()));
this.delete();
}
@@ -62,7 +68,7 @@ class InMemoryFile extends InMemoryNode implements File {
final ReadLock readLock = lock.readLock();
readLock.lock();
try {
final ReadableFile result = new InMemoryReadableFile(this::getContent, readLock);
final ReadableFile result = new InMemoryReadableFile(content::get, readLock);
success = true;
return result;
} finally {
@@ -79,7 +85,7 @@ class InMemoryFile extends InMemoryNode implements File {
writeLock.lock();
try {
create();
final WritableFile result = new InMemoryWritableFile(this::getContent, this::setContent, writeLock);
final WritableFile result = new InMemoryWritableFile(content::get, content::set, writeLock);
success = true;
return result;
} finally {
@@ -97,7 +103,7 @@ class InMemoryFile extends InMemoryNode implements File {
throw new UncheckedIOException(new FileAlreadyExistsException(k));
} else {
if (v == null) {
assert!content.hasRemaining();
assert!content.get().hasRemaining();
this.creationTime = Instant.now();
}
this.lastModified = Instant.now();
@@ -106,18 +112,9 @@ class InMemoryFile extends InMemoryNode implements File {
});
}
private ByteBuffer getContent() {
return content;
}
private void setContent(ByteBuffer content) {
this.content = content;
}
@Override
public void delete() {
content = ByteBuffer.allocate(INITIAL_SIZE);
content.flip();
content.set(createNewEmptyByteBuffer());
final InMemoryFolder parent = parent().get();
parent.existingChildren.computeIfPresent(this.name(), (k, v) -> {
// returning null removes the entry.

View File

@@ -86,8 +86,8 @@ abstract class InMemoryNode implements Node {
if (obj instanceof InMemoryNode) {
InMemoryNode other = (InMemoryNode) obj;
return this.getClass() == other.getClass() //
&& (this.parent == null && other.parent == null || this.parent.equals(other.parent)) //
&& (this.name == null && other.name == null || this.name.equals(other.name));
&& (this.parent == null && other.parent == null || this.parent != null && this.parent.equals(other.parent)) //
&& (this.name == null && other.name == null || this.name != null && this.name.equals(other.name));
} else {
return false;
}

View File

@@ -10,6 +10,8 @@ package org.cryptomator.filesystem.inmem;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.function.Supplier;
@@ -20,8 +22,8 @@ class InMemoryReadableFile implements ReadableFile {
private final Supplier<ByteBuffer> contentGetter;
private final ReadLock readLock;
private boolean open = true;
private volatile int position = 0;
private final AtomicInteger position = new AtomicInteger();
private final AtomicBoolean open = new AtomicBoolean(true);
public InMemoryReadableFile(Supplier<ByteBuffer> contentGetter, ReadLock readLock) {
this.contentGetter = contentGetter;
@@ -30,19 +32,21 @@ class InMemoryReadableFile implements ReadableFile {
@Override
public boolean isOpen() {
return open;
return open.get();
}
@Override
public int read(ByteBuffer destination) throws UncheckedIOException {
ByteBuffer source = contentGetter.get().asReadOnlyBuffer();
if (position >= source.limit()) {
int toBeCopied = destination.remaining();
int pos = position.getAndAdd(toBeCopied);
if (pos >= source.limit()) {
return -1;
} else {
source.position(position);
source.position(pos);
assert source.hasRemaining();
int numRead = ByteBuffers.copy(source, destination);
this.position += numRead;
assert numRead <= toBeCopied;
return numRead;
}
}
@@ -55,12 +59,12 @@ class InMemoryReadableFile implements ReadableFile {
@Override
public void position(long position) throws UncheckedIOException {
assert position < Integer.MAX_VALUE : "Can not use that big in-memory files.";
this.position = (int) position;
this.position.set((int) position);
}
@Override
public void close() throws UncheckedIOException {
open = false;
open.set(false);
readLock.unlock();
}

View File

@@ -10,6 +10,9 @@ package org.cryptomator.filesystem.inmem;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -22,9 +25,9 @@ public class InMemoryWritableFile implements WritableFile {
private final Supplier<ByteBuffer> contentGetter;
private final Consumer<ByteBuffer> contentSetter;
private final WriteLock writeLock;
private boolean open = true;
private volatile int position = 0;
private final AtomicInteger position = new AtomicInteger();
private final AtomicBoolean open = new AtomicBoolean(true);
private final ReentrantLock writingLock = new ReentrantLock();
public InMemoryWritableFile(Supplier<ByteBuffer> contentGetter, Consumer<ByteBuffer> contentSetter, WriteLock writeLock) {
this.contentGetter = contentGetter;
@@ -34,44 +37,65 @@ public class InMemoryWritableFile implements WritableFile {
@Override
public boolean isOpen() {
return open;
return open.get();
}
@Override
public void truncate() throws UncheckedIOException {
contentSetter.accept(ByteBuffer.allocate(0));
writingLock.lock();
try {
contentSetter.accept(InMemoryFile.createNewEmptyByteBuffer());
position.set(0);
} finally {
writingLock.unlock();
}
}
@Override
public int write(ByteBuffer source) throws UncheckedIOException {
ByteBuffer destination = contentGetter.get();
int oldFileSize = destination.limit();
int requiredSize = position + source.remaining();
int newFileSize = Math.max(oldFileSize, requiredSize);
if (destination.capacity() < requiredSize) {
ByteBuffer old = destination;
old.clear();
int newBufferSize = Math.max(requiredSize, (int) (destination.capacity() * InMemoryFile.GROWTH_RATE));
destination = ByteBuffer.allocate(newBufferSize);
ByteBuffers.copy(old, destination);
contentSetter.accept(destination);
writingLock.lock();
try {
ByteBuffer content = contentGetter.get();
int prevLimit = content.limit();
int toBeCopied = source.remaining();
int pos = position.getAndAdd(toBeCopied);
int ourLimit = pos + toBeCopied;
int newLimit = Math.max(prevLimit, ourLimit);
ByteBuffer destination = ensureCapacity(content, newLimit);
destination.limit(newLimit).position(pos);
return ByteBuffers.copy(source, destination);
} finally {
writingLock.unlock();
}
}
private ByteBuffer ensureCapacity(ByteBuffer buf, int limit) {
assert writingLock.isHeldByCurrentThread();
if (buf.capacity() < limit) {
int oldPos = buf.position();
buf.clear();
int newBufferSize = Math.max(limit, (int) (buf.capacity() * InMemoryFile.GROWTH_RATE));
ByteBuffer newBuf = ByteBuffer.allocate(newBufferSize);
ByteBuffers.copy(buf, newBuf);
newBuf.limit(limit).position(oldPos);
contentSetter.accept(newBuf);
return newBuf;
} else {
return buf;
}
destination.limit(newFileSize);
destination.position(position);
int numWritten = ByteBuffers.copy(source, destination);
this.position += numWritten;
return numWritten;
}
@Override
public void position(long position) throws UncheckedIOException {
assert position < Integer.MAX_VALUE : "Can not use that big in-memory files.";
this.position = (int) position;
this.position.set((int) position);
}
@Override
public void close() throws UncheckedIOException {
open = false;
open.set(false);
writeLock.unlock();
}

View File

@@ -9,7 +9,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>0.11.0</version>
<version>1.0.0</version>
</parent>
<artifactId>filesystem-invariants-tests</artifactId>
<name>Cryptomator filesystem: Invariants tests</name>

View File

@@ -17,6 +17,7 @@ import org.cryptomator.filesystem.inmem.InMemoryFileSystem;
import org.cryptomator.filesystem.invariants.FileSystemFactories.FileSystemFactory;
import org.cryptomator.filesystem.nio.NioFileSystem;
import org.cryptomator.filesystem.shortening.ShorteningFileSystem;
import org.cryptomator.filesystem.stats.StatsFileSystem;
import org.mockito.Mockito;
class FileSystemFactories implements Iterable<FileSystemFactory> {
@@ -28,10 +29,46 @@ class FileSystemFactories implements Iterable<FileSystemFactory> {
public FileSystemFactories() {
add("NioFileSystem", this::createNioFileSystem);
add("InMemoryFileSystem", this::createInMemoryFileSystem);
add("CryptoFileSystem(NioFileSystem)", this::createCryptoFileSystemNio);
add("CryptoFileSystem(InMemoryFileSystem)", this::createCryptoFileSystemInMemory);
add("ShorteningFileSystem(NioFileSystem)", this::createShorteningFileSystemNio);
add("ShorteningFileSystem(InMemoryFileSystem)", this::createShorteningFileSystemInMemory);
add("CryptoFileSystem > NioFileSystem", this::createCryptoFileSystemNio);
add("CryptoFileSystem > InMemoryFileSystem", this::createCryptoFileSystemInMemory);
add("ShorteningFileSystem > NioFileSystem", this::createShorteningFileSystemNio);
add("ShorteningFileSystem > InMemoryFileSystem", this::createShorteningFileSystemInMemory);
add("StatsFileSystem > NioFileSystem", this::createStatsFileSystemNio);
add("StatsFileSystem > InMemoryFileSystem", this::createStatsFileSystemInMemory);
add("StatsFileSystem > CryptoFileSystem > ShorteningFileSystem > InMemoryFileSystem", this::createCompoundFileSystemInMemory);
add("StatsFileSystem > CryptoFileSystem > ShorteningFileSystem > NioFileSystem", this::createCompoundFileSystemNio);
}
private FileSystem createCryptoFileSystemInMemory() {
return createCryptoFileSystem(createInMemoryFileSystem());
}
private FileSystem createCryptoFileSystemNio() {
return createCryptoFileSystem(createNioFileSystem());
}
private FileSystem createShorteningFileSystemNio() {
return createShorteningFileSystem(createNioFileSystem());
}
private FileSystem createShorteningFileSystemInMemory() {
return createShorteningFileSystem(createInMemoryFileSystem());
}
private FileSystem createStatsFileSystemNio() {
return createStatsFileSystem(createNioFileSystem());
}
private FileSystem createStatsFileSystemInMemory() {
return createStatsFileSystem(createInMemoryFileSystem());
}
private FileSystem createCompoundFileSystemNio() {
return createCompoundFileSystem(createNioFileSystem());
}
private FileSystem createCompoundFileSystemInMemory() {
return createCompoundFileSystem(createInMemoryFileSystem());
}
private FileSystem createNioFileSystem() {
@@ -46,25 +83,20 @@ class FileSystemFactories implements Iterable<FileSystemFactory> {
return new InMemoryFileSystem();
}
private FileSystem createCryptoFileSystemInMemory() {
FileSystem delegate = createInMemoryFileSystem();
private FileSystem createCompoundFileSystem(FileSystem delegate) {
return createStatsFileSystem(createCryptoFileSystem(createShorteningFileSystem(delegate)));
}
private FileSystem createStatsFileSystem(FileSystem delegate) {
return new StatsFileSystem(delegate);
}
private FileSystem createCryptoFileSystem(FileSystem delegate) {
CRYPTO_FS_COMP.cryptoFileSystemFactory().initializeNew(delegate, "aPassphrase");
return CRYPTO_FS_COMP.cryptoFileSystemFactory().unlockExisting(delegate, "aPassphrase", Mockito.mock(CryptoFileSystemDelegate.class));
}
private FileSystem createCryptoFileSystemNio() {
FileSystem delegate = createNioFileSystem();
CRYPTO_FS_COMP.cryptoFileSystemFactory().initializeNew(delegate, "aPassphrase");
return CRYPTO_FS_COMP.cryptoFileSystemFactory().unlockExisting(delegate, "aPassphrase", Mockito.mock(CryptoFileSystemDelegate.class));
}
private FileSystem createShorteningFileSystemNio() {
FileSystem delegate = createNioFileSystem();
return new ShorteningFileSystem(delegate, "m", 3);
}
private FileSystem createShorteningFileSystemInMemory() {
FileSystem delegate = createInMemoryFileSystem();
private FileSystem createShorteningFileSystem(FileSystem delegate) {
return new ShorteningFileSystem(delegate, "m", 3);
}

View File

@@ -12,7 +12,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>0.11.0</version>
<version>1.0.0</version>
</parent>
<artifactId>filesystem-nameshortening</artifactId>
<name>Cryptomator filesystem: Name shortening layer</name>

View File

@@ -10,6 +10,7 @@ package org.cryptomator.filesystem.shortening;
import java.io.FileNotFoundException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -44,7 +45,7 @@ class FilenameShortener {
if (longName.length() < threshold) {
return longName;
} else {
final byte[] hashBytes = SHA1.get().digest(longName.getBytes());
final byte[] hashBytes = SHA1.get().digest(longName.getBytes(StandardCharsets.UTF_8));
final String hash = BASE32.encodeAsString(hashBytes);
return hash + LONG_NAME_FILE_EXT;
}

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>0.11.0</version>
<version>1.0.0</version>
</parent>
<artifactId>filesystem-nio</artifactId>
<name>Cryptomator filesystem: NIO-based physical layer</name>

View File

@@ -12,7 +12,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>0.11.0</version>
<version>1.0.0</version>
</parent>
<artifactId>filesystem-stats</artifactId>
<name>Cryptomator filesystem: Throughput statistics</name>

View File

@@ -12,7 +12,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>0.11.0</version>
<version>1.0.0</version>
</parent>
<artifactId>frontend-api</artifactId>
<name>Cryptomator frontend: API</name>

View File

@@ -12,7 +12,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>0.11.0</version>
<version>1.0.0</version>
</parent>
<artifactId>frontend-webdav</artifactId>
<name>Cryptomator frontend: WebDAV frontend</name>

View File

@@ -32,11 +32,15 @@ import org.cryptomator.filesystem.File;
import org.cryptomator.filesystem.Folder;
import org.cryptomator.filesystem.ReadableFile;
import org.cryptomator.filesystem.jackrabbit.FileLocator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.io.ByteStreams;
class DavFile extends DavNode<FileLocator> {
private static final Logger LOG = LoggerFactory.getLogger(DavFile.class);
public DavFile(FilesystemResourceFactory factory, LockManager lockManager, DavSession session, FileLocator node) {
super(factory, lockManager, session, node);
}
@@ -128,7 +132,7 @@ class DavFile extends DavNode<FileLocator> {
@Override
public DavProperty<?> getProperty(DavPropertyName name) {
if (DavPropertyName.GETCONTENTLENGTH.equals(name)) {
return sizeProperty().get();
return sizeProperty().orElse(null);
} else {
return super.getProperty(name);
}
@@ -147,6 +151,9 @@ class DavFile extends DavNode<FileLocator> {
if (node.exists()) {
try (ReadableFile src = node.openReadable()) {
return Optional.of(new DefaultDavProperty<Long>(DavPropertyName.GETCONTENTLENGTH, src.size()));
} catch (RuntimeException e) {
LOG.warn("Could not determine file size of " + getResourcePath(), e);
return Optional.empty();
}
} else {
return Optional.empty();
@@ -167,7 +174,9 @@ class DavFile extends DavNode<FileLocator> {
public ActiveLock lock(LockInfo reqLockInfo) throws DavException {
ActiveLock lock = super.lock(reqLockInfo);
if (!exists()) {
getCollection().addMember(this, new NullInputContext());
DavFolder parentFolder = getCollection();
assert parentFolder != null : "File always has a folder.";
parentFolder.addMember(this, new NullInputContext());
}
return lock;
}

View File

@@ -71,7 +71,9 @@ class DavFileWithRange extends DavFile {
try {
final Long lower = requestRange.getLeft().isEmpty() ? null : Long.valueOf(requestRange.getLeft());
final Long upper = requestRange.getRight().isEmpty() ? null : Long.valueOf(requestRange.getRight());
if (lower == null) {
if (lower == null && upper == null) {
return new ImmutablePair<Long, Long>(0l, contentLength - 1);
} else if (lower == null) {
return new ImmutablePair<Long, Long>(contentLength - upper, contentLength - 1);
} else if (upper == null) {
return new ImmutablePair<Long, Long>(lower, contentLength - 1);

View File

@@ -37,6 +37,7 @@ import org.apache.jackrabbit.webdav.property.DavPropertySet;
import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
import org.apache.jackrabbit.webdav.property.PropEntry;
import org.cryptomator.filesystem.jackrabbit.FileSystemResourceLocator;
import org.cryptomator.filesystem.jackrabbit.FolderLocator;
abstract class DavNode<T extends FileSystemResourceLocator> implements DavResource {
@@ -187,18 +188,14 @@ abstract class DavNode<T extends FileSystemResourceLocator> implements DavResour
}
@Override
public DavResource getCollection() {
public DavFolder getCollection() {
if (node.isRootLocation()) {
return null;
}
assert node.parent().isPresent() : "as my mom always sais: if it's not root, it has a parent";
final FileSystemResourceLocator parentPath = node.parent().get();
try {
return factory.createResource(parentPath, session);
} catch (DavException e) {
throw new IllegalStateException("Unable to get parent resource with path " + parentPath, e);
}
final FolderLocator parentPath = node.parent().get();
return factory.createFolder(parentPath, session);
}
@Override

View File

@@ -158,7 +158,7 @@ class ExclusiveSharedLockManager implements LockManager {
}
// or otherwise look for parent locks:
if (locator.parent().isPresent()) {
return getLockInternal(type, scope, locator.parent().get(), depth++);
return getLockInternal(type, scope, locator.parent().get(), depth + 1);
} else {
return null;
}

View File

@@ -50,7 +50,7 @@ class FilesystemResourceFactory implements DavResourceFactory {
}
@Override
public DavResource createResource(DavResourceLocator locator, DavSession session) throws DavException {
public DavResource createResource(DavResourceLocator locator, DavSession session) {
if (locator instanceof FolderLocator) {
FolderLocator folder = (FolderLocator) locator;
return createFolder(folder, session);

View File

@@ -30,6 +30,7 @@ public final class CommandResult {
/**
* Constructs a CommandResult from a terminated process and closes all its streams.
*
* @param process An <strong>already finished</strong> process.
*/
CommandResult(Process process) {
@@ -52,7 +53,7 @@ public final class CommandResult {
logDebugInfo();
}
}
/**
* @return Data written to STDOUT
*/
@@ -94,10 +95,10 @@ public final class CommandResult {
assertNoException();
int exitValue = getExitValue();
if (exitValue != 0) {
throw new CommandFailedException(format("Command execution failed. Exit code: %d\n" + "# Output:\n" + "%s\n" + "# Error:\n" + "%s", exitValue, stdout, stderr));
throw new CommandFailedException(format("Command execution failed. Exit code: %d %n# Output:%n%s %n# Error: %n%s", exitValue, stdout, stderr));
}
}
private void assertNoException() throws CommandFailedException {
if (exception != null) {
throw exception;

View File

@@ -20,15 +20,15 @@ import java.util.concurrent.locks.ReentrantLock;
import org.cryptomator.frontend.CommandFailedException;
final class FutureCommandResult implements Future<CommandResult>, Runnable {
private final Process process;
private final AtomicBoolean canceled = new AtomicBoolean();
private final AtomicBoolean done = new AtomicBoolean();
private final Lock lock = new ReentrantLock();
private final Condition doneCondition = lock.newCondition();
private CommandFailedException exception;
FutureCommandResult(Process process) {
this.process = process;
}
@@ -49,7 +49,7 @@ final class FutureCommandResult implements Future<CommandResult>, Runnable {
public boolean isCancelled() {
return canceled.get();
}
private void setDone() {
lock.lock();
try {
@@ -69,7 +69,7 @@ final class FutureCommandResult implements Future<CommandResult>, Runnable {
public CommandResult get() throws InterruptedException, ExecutionException {
lock.lock();
try {
while(!done.get()) {
while (!done.get()) {
doneCondition.await();
}
} finally {
@@ -85,8 +85,12 @@ final class FutureCommandResult implements Future<CommandResult>, Runnable {
public CommandResult get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
lock.lock();
try {
while(!done.get()) {
doneCondition.await(timeout, unit);
boolean doneInTime = true;
if (!done.get()) {
doneInTime = doneCondition.await(timeout, unit);
}
if (!doneInTime) {
throw new TimeoutException();
}
} finally {
lock.unlock();

View File

@@ -7,7 +7,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>0.11.0</version>
<version>1.0.0</version>
<packaging>pom</packaging>
<name>Cryptomator</name>

View File

@@ -12,7 +12,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>0.11.0</version>
<version>1.0.0</version>
</parent>
<artifactId>uber-jar</artifactId>
<packaging>pom</packaging>

View File

@@ -12,7 +12,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>0.11.0</version>
<version>1.0.0</version>
</parent>
<artifactId>ui</artifactId>
<name>Cryptomator GUI</name>

View File

@@ -109,7 +109,7 @@ public class MacWarningsController extends AbstractFXMLViewController {
@FXML
private void didClickMoreInformationButton(ActionEvent event) {
application.getHostServices().showDocument("https://cryptomator.org/help.html#macWarning");
application.getHostServices().showDocument("https://cryptomator.org/faq/#macWarning");
}
private void unauthenticatedResourcesDidChange(Change<? extends String> change) {

View File

@@ -36,6 +36,7 @@ import org.slf4j.LoggerFactory;
import dagger.Lazy;
import javafx.application.Platform;
import javafx.beans.binding.Binding;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
@@ -120,6 +121,9 @@ public class MainController extends AbstractFXMLViewController {
@FXML
private Pane contentPane;
@FXML
private Pane emptyListInstructions;
@Override
public void initialize() {
vaultList.setItems(vaults);
@@ -127,6 +131,7 @@ public class MainController extends AbstractFXMLViewController {
activeController.set(welcomeController.get());
selectedVault.bind(vaultList.getSelectionModel().selectedItemProperty());
removeVaultButton.disableProperty().bind(canEditSelectedVault);
emptyListInstructions.visibleProperty().bind(Bindings.isEmpty(vaults));
EasyBind.subscribe(activeController, this::activeControllerDidChange);
EasyBind.subscribe(selectedVault, this::selectedVaultDidChange);
@@ -231,6 +236,9 @@ public class MainController extends AbstractFXMLViewController {
@FXML
private void didClickRemoveSelectedEntry(ActionEvent e) {
vaults.remove(selectedVault.get());
if (vaults.isEmpty()) {
activeController.set(welcomeController.get());
}
}
@FXML

View File

@@ -9,6 +9,7 @@
package org.cryptomator.ui.controllers;
import java.net.URL;
import java.util.Optional;
import java.util.ResourceBundle;
import javax.inject.Inject;
@@ -21,6 +22,7 @@ import org.fxmisc.easybind.EasyBind;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
@@ -42,12 +44,16 @@ public class SettingsController extends AbstractFXMLViewController {
@FXML
private TextField portField;
@FXML
private Label versionLabel;
@Override
public void initialize() {
checkForUpdatesCheckbox.setDisable(areUpdatesManagedExternally());
checkForUpdatesCheckbox.setSelected(settings.isCheckForUpdatesEnabled() && !areUpdatesManagedExternally());
portField.setText(String.valueOf(settings.getPort()));
portField.addEventFilter(KeyEvent.KEY_TYPED, this::filterNumericKeyEvents);
versionLabel.setText(String.format(localization.getString("settings.version.label"), applicationVersion().orElse("SNAPSHOT")));
EasyBind.subscribe(portField.textProperty(), this::portDidChange);
EasyBind.subscribe(checkForUpdatesCheckbox.selectedProperty(), settings::setCheckForUpdatesEnabled);
@@ -63,6 +69,10 @@ public class SettingsController extends AbstractFXMLViewController {
return localization;
}
private Optional<String> applicationVersion() {
return Optional.ofNullable(getClass().getPackage().getImplementationVersion());
}
private void portDidChange(String newValue) {
try {
int port = Integer.parseInt(newValue);

View File

@@ -164,6 +164,7 @@ public class UnlockedController extends AbstractFXMLViewController {
private void didClickCopyUrl(ActionEvent event) {
ClipboardContent clipboardContent = new ClipboardContent();
clipboardContent.putUrl(vault.get().getWebDavUrl());
clipboardContent.putString(vault.get().getWebDavUrl());
Clipboard.getSystemClipboard().setContent(clipboardContent);
}
@@ -185,9 +186,9 @@ public class UnlockedController extends AbstractFXMLViewController {
private void startIoSampling() {
final Series<Number, Number> decryptedBytes = new Series<>();
decryptedBytes.setName("decrypted");
decryptedBytes.setName(localization.getString("unlocked.label.statsDecrypted"));
final Series<Number, Number> encryptedBytes = new Series<>();
encryptedBytes.setName("encrypted");
encryptedBytes.setName(localization.getString("unlocked.label.statsEncrypted"));
ioGraph.getData().add(decryptedBytes);
ioGraph.getData().add(encryptedBytes);

View File

@@ -124,7 +124,9 @@ public class WelcomeController extends AbstractFXMLViewController {
final ObjectMapper mapper = new ObjectMapper();
final Map<String, String> map = mapper.readValue(responseData, new TypeReference<HashMap<String, String>>() {
});
this.compareVersions(map);
if (map != null) {
this.compareVersions(map);
}
}
} catch (IOException e) {
// no error handling required. Maybe next time the version check is successful.

View File

@@ -9,7 +9,6 @@
package org.cryptomator.ui.model;
import java.io.IOException;
import java.io.Serializable;
import java.io.UncheckedIOException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Path;
@@ -51,9 +50,7 @@ import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class Vault implements Serializable, CryptoFileSystemDelegate {
private static final long serialVersionUID = 3754487289683599469L;
public class Vault implements CryptoFileSystemDelegate {
public static final String VAULT_FILE_EXTENSION = ".cryptomator";
@@ -169,7 +166,7 @@ public class Vault implements Serializable, CryptoFileSystemDelegate {
@Override
public boolean shouldSkipAuthentication(String cleartextPath) {
return namesOfResourcesWithInvalidMac.contains(cleartextPath);
return whitelistedResourcesWithInvalidMac.contains(cleartextPath);
}
// ******************************************************************************

View File

@@ -27,6 +27,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.fasterxml.jackson.databind.module.SimpleModule;
@Singleton
@@ -49,7 +50,7 @@ public class VaultObjectMapperProvider implements Provider<ObjectMapper> {
return om;
}
private class VaultSerializer extends JsonSerializer<Vault> {
private static class VaultSerializer extends JsonSerializer<Vault> {
@Override
public void serialize(Vault value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
@@ -70,6 +71,9 @@ public class VaultObjectMapperProvider implements Provider<ObjectMapper> {
@Override
public Vault deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
final JsonNode node = jp.readValueAsTree();
if (node == null || !node.has("path")) {
throw new InvalidFormatException("Node is null or doesn't contain a path.", node, Vault.class);
}
final String pathStr = node.get("path").asText();
final Path path = FileSystems.getDefault().getPath(pathStr);
final Vault vault = vaultFactoy.createVault(path);

View File

@@ -24,7 +24,7 @@ public class Settings implements Serializable {
public static final int MIN_PORT = 1024;
public static final int MAX_PORT = 65535;
public static final int DEFAULT_PORT = 0;
public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
public static final Integer DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
@JsonProperty("directories")
private List<Vault> directories;

View File

@@ -22,6 +22,7 @@ import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.Objects;
import java.util.Optional;
@@ -68,7 +69,7 @@ public class SingleInstanceManager {
*/
public boolean sendMessage(String string, long timeout) throws IOException {
Objects.requireNonNull(string);
byte[] message = string.getBytes();
byte[] message = string.getBytes(StandardCharsets.UTF_8);
if (message.length >= 256 * 256) {
throw new IOException("Message too long.");
}
@@ -84,7 +85,7 @@ public class SingleInstanceManager {
return true;
}
return !buf.hasRemaining();
}, timeout, 10);
} , timeout, 10);
return !buf.hasRemaining();
}
@@ -107,7 +108,7 @@ public class SingleInstanceManager {
*/
public static class LocalInstance implements Closeable {
private class ChannelState {
ByteBuffer write = ByteBuffer.wrap(applicationKey.getBytes());
ByteBuffer write = ByteBuffer.wrap(applicationKey.getBytes(StandardCharsets.UTF_8));
ByteBuffer readLength = ByteBuffer.allocate(2);
ByteBuffer readMessage = null;
}
@@ -139,10 +140,17 @@ public class SingleInstanceManager {
void handleSelection(SelectionKey key) throws IOException {
if (key.isAcceptable()) {
final SocketChannel accepted = channel.accept();
if (accepted != null) {
LOG.debug("accepted incoming connection");
accepted.configureBlocking(false);
accepted.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
SelectionKey keyOfAcceptedConnection = null;
try {
if (accepted != null) {
LOG.debug("accepted incoming connection");
accepted.configureBlocking(false);
keyOfAcceptedConnection = accepted.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
}
} finally {
if (keyOfAcceptedConnection == null) {
accepted.close();
}
}
}
@@ -189,6 +197,7 @@ public class SingleInstanceManager {
}
}
@Override
public void close() {
IOUtils.closeQuietly(selector);
IOUtils.closeQuietly(channel);
@@ -257,7 +266,7 @@ public class SingleInstanceManager {
LOG.debug("connected to instance {}", port.get());
final byte[] bytes = applicationKey.getBytes();
final byte[] bytes = applicationKey.getBytes(StandardCharsets.UTF_8);
ByteBuffer buf = ByteBuffer.allocate(bytes.length);
tryFill(channel, buf, 1000);
if (buf.hasRemaining()) {
@@ -359,7 +368,7 @@ public class SingleInstanceManager {
}
}
return !buf.hasRemaining();
}, timeout, 1);
} , timeout, 1);
}
}
}

View File

@@ -65,7 +65,7 @@
.button,
.toggle-button {
-fx-pref-height: 25px;
-fx-pref-height: 27px;
-fx-background-color: COLOR_BORDER, linear-gradient(to bottom, #F0F0F0 0%, #E5E5E5 100%);
-fx-background-insets: 0, 1;
-fx-padding: 2px 12px 2px 12px;

View File

@@ -20,6 +20,9 @@
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.shape.Arc?>
<HBox fx:id="rootPane" prefHeight="440.0" prefWidth="652.0" spacing="12.0" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
@@ -49,7 +52,15 @@
</fx:define>
<VBox prefWidth="200.0" cacheShape="true" cache="true">
<ListView fx:id="vaultList" VBox.vgrow="ALWAYS" focusTraversable="false" cacheShape="true" cache="true"/>
<StackPane VBox.vgrow="ALWAYS" cacheShape="true" cache="true">
<ListView fx:id="vaultList" focusTraversable="false" cacheShape="true" cache="true"/>
<AnchorPane fx:id="emptyListInstructions" cacheShape="true" cache="true">
<HBox AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.bottomAnchor="100.0" alignment="CENTER" cacheShape="true" cache="true">
<Label textAlignment="CENTER" text="%main.emptyListInstructions" wrapText="true" cacheShape="true" cache="true"/>
</HBox>
<Arc AnchorPane.bottomAnchor="5.0" type="OPEN" centerX="-10.0" centerY="0.0" radiusY="100.0" radiusX="60.0" startAngle="0" length="-60.0" strokeWidth="1" stroke="BLACK" fill="TRANSPARENT"/>
</AnchorPane>
</StackPane>
<ToolBar VBox.vgrow="NEVER" styleClass="list-related-toolbar" cacheShape="true" cache="true">
<items>
<ToggleButton text="&#xf489;" styleClass="ionicons" fx:id="addVaultButton" onAction="#didClickAddVault" focusTraversable="false" cacheShape="true" cache="true"/>

View File

@@ -16,8 +16,9 @@
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.VBox?>
<VBox prefWidth="400.0" alignment="TOP_CENTER" spacing="12.0" cacheShape="true" cache="true">
<GridPane VBox.vgrow="ALWAYS" vgap="12.0" hgap="12.0" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
<VBox prefWidth="400.0" alignment="TOP_CENTER" spacing="12.0" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
<Label VBox.vgrow="NEVER" fx:id="versionLabel" alignment="CENTER" cacheShape="true" cache="true" />
<GridPane VBox.vgrow="ALWAYS" vgap="12.0" hgap="12.0" cacheShape="true" cache="true">
<padding>
<Insets top="24.0" right="12.0" bottom="24.0" left="12.0" />
</padding>

View File

@@ -28,9 +28,12 @@
<fx:define>
<ContextMenu fx:id="moreOptionsMenu">
<items>
<MenuItem text="%unlocked.moreOptions.reveal" onAction="#didClickRevealVault"/>
<!-- Future use: -->
<MenuItem text="%unlocked.moreOptions.copyUrl" onAction="#didClickCopyUrl"/>
<MenuItem text="%unlocked.moreOptions.reveal" onAction="#didClickRevealVault">
<graphic><Label text="&#xf133;" styleClass="ionicons"/></graphic>
</MenuItem>
<MenuItem text="%unlocked.moreOptions.copyUrl" onAction="#didClickCopyUrl">
<graphic><Label text="&#xf376;" styleClass="ionicons"/></graphic>
</MenuItem>
</items>
</ContextMenu>
</fx:define>

View File

@@ -20,7 +20,7 @@
<VBox prefWidth="400.0" prefHeight="400.0" spacing="24.0" alignment="CENTER" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
<VBox fx:id="checkForUpdatesContainer" spacing="6.0" alignment="CENTER" cacheShape="true" cache="true">
<VBox fx:id="checkForUpdatesContainer" spacing="6.0" alignment="CENTER" cacheShape="true" cache="true" prefHeight="50.0">
<HBox alignment="CENTER" spacing="5.0" cacheShape="true" cache="true">
<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" />
@@ -28,8 +28,10 @@
<Hyperlink alignment="CENTER" fx:id="updateLink" onAction="#didClickUpdateLink" cacheShape="true" cache="true" />
</VBox>
<ImageView fitHeight="200.0" preserveRatio="true" smooth="false" cache="true">
<ImageView fitHeight="200.0" preserveRatio="true" smooth="false" cache="true" style="-fx-background-color: green;">
<Image url="/bot_welcome.png"/>
</ImageView>
<VBox prefHeight="50.0"/>
</VBox>

View File

@@ -10,6 +10,7 @@
app.name=Cryptomator
# main.fxml
main.emptyListInstructions=Click here to add a vault
main.directoryList.contextMenu.remove=Remove from list
main.directoryList.contextMenu.changePassword=Change password
main.addDirectory.contextMenu.new=Create new vault
@@ -36,8 +37,8 @@ unlock.button.unlock=Unlock vault
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.
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.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.
@@ -49,19 +50,21 @@ changePassword.label.newPassword=New password
changePassword.label.retypePassword=Retype password
changePassword.label.downloadsPageLink=All Cryptomator versions
changePassword.button.change=Change password
changePassword.errorMessage.wrongPassword=Wrong password.
changePassword.errorMessage.decryptionFailed=Decryption failed.
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.
changePassword.infoMessage.success=Password changed
# unlocked.fxml
unlocked.button.lock=Lock vault
unlocked.moreOptions.reveal=Reveal drive
unlocked.moreOptions.copyUrl=Copy WebDAV URL
unlocked.label.revealFailed=Command failed.
unlocked.label.unmountFailed=Ejecting drive failed.
unlocked.label.revealFailed=Command failed
unlocked.label.unmountFailed=Ejecting drive failed
unlocked.label.statsEncrypted=encrypted
unlocked.label.statsDecrypted=decrypted
unlocked.ioGraph.yAxis.label=Throughput (MiB/s)
# mac_warnings.fxml
@@ -71,6 +74,7 @@ macWarnings.moreInformationButton=Learn more
macWarnings.whitelistButton=Decrypt selected anyway
# settings.fxml
settings.version.label=Version %s
settings.checkForUpdates.label=Check for updates
settings.port.label=WebDAV Port *
settings.port.prompt=0 = Choose automatically

View File

@@ -0,0 +1,88 @@
#-------------------------------------------------------------------------------
# Copyright (c) 2016 Markus Kreusch
# This file is licensed under the terms of the MIT license.
# See the LICENSE.txt file for more info.
#
# Contributors:
# Markus Kreusch - initial API and implementation
#-------------------------------------------------------------------------------
app.name=Cryptomator
# main.fxml
main.emptyListInstructions=Klicken Sie hier, um neue Tresore hinzuzufügen
main.directoryList.contextMenu.remove=Aus Liste entfernen
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.
# initialize.fxml
initialize.label.password=Passwort
initialize.label.retypePassword=Passwort bestätigen
initialize.button.ok=Tresor erstellen
initialize.messageLabel.alreadyInitialized=Tresor bereits vorhanden
initialize.messageLabel.initializationFailed=Fehler beim Initialisieren. Details in der Log-Datei.
# unlock.fxml
unlock.label.password=Passwort
unlock.label.mountName=Laufwerksname
unlock.label.winDriveLetter=Laufwerksbuchstabe
unlock.label.downloadsPageLink=Alle Cryptomator Versionen
unlock.label.advancedHeading=Erweiterte Optionen
unlock.button.unlock=Tresor entsperren
unlock.button.advancedOptions.show=Weitere Optionen
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.
# change_password.fxml
changePassword.label.oldPassword=Altes Passwort
changePassword.label.newPassword=Neues Passwort
changePassword.label.retypePassword=Passwort bestätigen
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
unlocked.moreOptions.reveal=Laufwerk anzeigen
unlocked.moreOptions.copyUrl=WebDAV-URL kopieren
unlocked.label.revealFailed=Befehl fehlgeschlagen
unlocked.label.unmountFailed=Trennen des Laufwerks fehlgeschlagen
unlocked.label.statsEncrypted=verschlüsselt
unlocked.label.statsDecrypted=entschlüsselt
unlocked.ioGraph.yAxis.label=Durchsatz (MiB/s)
# mac_warnings.fxml
macWarnings.windowTitle=Achtung - Kompromittierte Datei in %s
macWarnings.message=Cryptomator hat möglicherweise unerlaubte Veränderungen in den folgenden Dateien erkannt:
macWarnings.moreInformationButton=Mehr erfahren
macWarnings.whitelistButton=Trotzdem entschlüsseln
# settings.fxml
settings.version.label=Version %s
settings.checkForUpdates.label=Auf Updates prüfen
settings.port.label=WebDAV Port *
settings.port.prompt=0 = Automatisch wählen
settings.requiresRestartLabel=* benötigt Neustart von Cryptomator
# tray icon
tray.menu.open=Öffnen
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.