first compile-clean attempt to integrate the layered I/O subsystem with the existing UI

This commit is contained in:
Sebastian Stenzel
2016-01-26 20:17:33 +01:00
parent e4d626eef5
commit c56d0b7d4a
42 changed files with 539 additions and 430 deletions

View File

@@ -2,12 +2,10 @@ package org.cryptomator.filesystem.crypto;
import javax.inject.Singleton;
import org.cryptomator.crypto.engine.impl.CryptoEngineModule;
import dagger.Component;
@Singleton
@Component(modules = CryptoEngineModule.class)
@Component(modules = CryptoFileSystemModule.class)
interface CryptoFileSystemComponent {
CryptoFileSystemFactory cryptoFileSystemFactory();

View File

@@ -5,6 +5,7 @@ import javax.inject.Provider;
import javax.inject.Singleton;
import org.cryptomator.crypto.engine.Cryptor;
import org.cryptomator.crypto.engine.InvalidPassphraseException;
import org.cryptomator.filesystem.FileSystem;
import org.cryptomator.filesystem.Folder;
import org.cryptomator.filesystem.blockaligned.BlockAlignedFileSystemFactory;
@@ -24,7 +25,7 @@ public class CryptoFileSystemFactory {
this.blockAlignedFileSystemFactory = blockAlignedFileSystemFactory;
}
public FileSystem get(Folder root, CharSequence passphrase, CryptoFileSystemDelegate delegate) {
public FileSystem get(Folder root, CharSequence passphrase, CryptoFileSystemDelegate delegate) throws InvalidPassphraseException {
final FileSystem nameShorteningFs = shorteningFileSystemFactory.get(root);
final FileSystem cryptoFs = new CryptoFileSystem(nameShorteningFs, cryptorProvider.get(), delegate, passphrase);
return blockAlignedFileSystemFactory.get(cryptoFs);

View File

@@ -0,0 +1,10 @@
package org.cryptomator.filesystem.crypto;
import org.cryptomator.crypto.engine.impl.CryptoEngineModule;
import dagger.Module;
@Module(includes = CryptoEngineModule.class)
public class CryptoFileSystemModule {
}

1
main/frontend-api/.gitignore vendored Normal file
View File

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

27
main/frontend-api/pom.xml Normal file
View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2016 Sebastian Stenzel
This file is licensed under the terms of the MIT license.
See the LICENSE.txt file for more info.
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>0.11.0-SNAPSHOT</version>
</parent>
<artifactId>frontend-api</artifactId>
<name>Cryptomator frontend: API</name>
<description>API for filesystem frontends</description>
<dependencies>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>filesystem-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,18 @@
package org.cryptomator.frontend;
import java.util.Map;
import java.util.Optional;
public interface Frontend extends AutoCloseable {
public enum MountParam {
MOUNT_NAME, WIN_DRIVE_LETTER
}
boolean mount(Map<MountParam, Optional<String>> map);
void unmount();
void reveal();
}

View File

@@ -0,0 +1,9 @@
package org.cryptomator.frontend;
public class FrontendCreationFailedException extends Exception {
public FrontendCreationFailedException(Throwable cause) {
super(cause);
}
}

View File

@@ -0,0 +1,17 @@
package org.cryptomator.frontend;
import org.cryptomator.filesystem.Folder;
public interface FrontendFactory {
/**
* Provides a new frontend to access the given folder.
*
* @param root Root resource accessible through this frontend.
* @param uniqueName Name of the frontend, i.e. used to create subresources for the different frontends inside of a common virtual drive.
* @return A new frontend
* @throws FrontendCreationFailedException If creation was not possible.
*/
Frontend create(Folder root, String uniqueName) throws FrontendCreationFailedException;
}

View File

@@ -0,0 +1,12 @@
/*******************************************************************************
* Copyright (c) 2015 Sebastian Stenzel and others.
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
*******************************************************************************/
/**
* Provides frontends for {@link org.cryptomator.filesystem.FileSystem FileSystems}.
*/
package org.cryptomator.frontend;

View File

@@ -15,7 +15,7 @@
<version>0.11.0-SNAPSHOT</version>
</parent>
<artifactId>frontend-webdav</artifactId>
<name>Jackrabbit-based WebDAV filesystem frontend</name>
<name>Cryptomator frontend: WebDAV frontend</name>
<description>Provides access via WebDAV to filesystems</description>
<properties>
@@ -32,6 +32,10 @@
<groupId>org.cryptomator</groupId>
<artifactId>commons</artifactId>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>frontend-api</artifactId>
</dependency>
<!-- Jackrabbit -->
<dependency>

View File

@@ -1,4 +1,4 @@
package org.cryptomator.webdav.server;
package org.cryptomator.frontend.webdav;
import javax.inject.Singleton;

View File

@@ -0,0 +1,72 @@
package org.cryptomator.frontend.webdav;
import java.net.URI;
import java.util.Map;
import java.util.Optional;
import org.cryptomator.frontend.Frontend;
import org.cryptomator.frontend.FrontendCreationFailedException;
import org.cryptomator.frontend.webdav.mount.CommandFailedException;
import org.cryptomator.frontend.webdav.mount.WebDavMount;
import org.cryptomator.frontend.webdav.mount.WebDavMounterProvider;
import org.eclipse.jetty.servlet.ServletContextHandler;
class WebDavFrontend implements Frontend {
private final WebDavMounterProvider webdavMounterProvider;
private final ServletContextHandler handler;
private final URI uri;
private WebDavMount mount;
public WebDavFrontend(WebDavMounterProvider webdavMounterProvider, ServletContextHandler handler, URI uri) throws FrontendCreationFailedException {
this.webdavMounterProvider = webdavMounterProvider;
this.handler = handler;
this.uri = uri;
try {
handler.start();
} catch (Exception e) {
throw new FrontendCreationFailedException(e);
}
}
@Override
public void close() throws Exception {
unmount();
handler.stop();
}
@Override
public boolean mount(Map<MountParam, Optional<String>> mountParams) {
try {
mount = webdavMounterProvider.get().mount(uri, mountParams);
return true;
} catch (CommandFailedException e) {
return false;
}
}
@Override
public void unmount() {
if (mount != null) {
try {
mount.unmount();
} catch (CommandFailedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
@Override
public void reveal() {
if (mount != null) {
try {
mount.reveal();
} catch (CommandFailedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

View File

@@ -1,4 +1,4 @@
package org.cryptomator.webdav.server;
package org.cryptomator.frontend.webdav;
import java.net.URI;
import java.net.URISyntaxException;
@@ -10,6 +10,10 @@ import javax.inject.Singleton;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.filesystem.Folder;
import org.cryptomator.frontend.Frontend;
import org.cryptomator.frontend.FrontendCreationFailedException;
import org.cryptomator.frontend.FrontendFactory;
import org.cryptomator.frontend.webdav.mount.WebDavMounterProvider;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
@@ -21,7 +25,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton
public class WebDavServer {
public class WebDavServer implements FrontendFactory {
private static final Logger LOG = LoggerFactory.getLogger(WebDavServer.class);
private static final String LOCALHOST = SystemUtils.IS_OS_WINDOWS ? "::1" : "localhost";
@@ -34,15 +38,17 @@ public class WebDavServer {
private final ServerConnector localConnector;
private final ContextHandlerCollection servletCollection;
private final WebDavServletContextFactory servletContextFactory;
private final WebDavMounterProvider webdavMounterProvider;
@Inject
WebDavServer(WebDavServletContextFactory servletContextFactory) {
WebDavServer(WebDavServletContextFactory servletContextFactory, WebDavMounterProvider webdavMounterProvider) {
final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(MAX_PENDING_REQUESTS);
final ThreadPool tp = new QueuedThreadPool(MAX_THREADS, MIN_THREADS, THREAD_IDLE_SECONDS, queue);
this.server = new Server(tp);
this.localConnector = new ServerConnector(server);
this.servletCollection = new ContextHandlerCollection();
this.servletContextFactory = servletContextFactory;
this.webdavMounterProvider = webdavMounterProvider;
localConnector.setHost(LOCALHOST);
server.setConnectors(new Connector[] {localConnector});
@@ -82,17 +88,27 @@ public class WebDavServer {
}
}
public ServletContextHandler addServlet(Folder root, String contextPath) {
// visible for testing
ServletContextHandler addServlet(Folder root, URI contextRoot) {
ServletContextHandler handler = servletContextFactory.create(contextRoot, root);
servletCollection.addHandler(handler);
servletCollection.mapContexts();
return handler;
}
@Override
public Frontend create(Folder root, String contextPath) throws FrontendCreationFailedException {
if (!contextPath.startsWith("/")) {
throw new IllegalArgumentException("contextPath must begin with '/'");
}
final URI uri;
try {
uri = new URI("http", null, LOCALHOST, getPort(), contextPath, null, null);
} catch (URISyntaxException e) {
throw new IllegalStateException(e);
}
ServletContextHandler handler = servletContextFactory.create(uri, root);
servletCollection.addHandler(handler);
servletCollection.mapContexts();
return handler;
final ServletContextHandler handler = addServlet(root, uri);
return new WebDavFrontend(webdavMounterProvider, handler, uri);
}
}

View File

@@ -6,7 +6,7 @@
* Contributors:
* Sebastian Stenzel - initial API and implementation
*******************************************************************************/
package org.cryptomator.webdav.server;
package org.cryptomator.frontend.webdav;
import java.net.URI;
import java.util.EnumSet;

View File

@@ -1,4 +1,4 @@
package org.cryptomator.ui.util.mount;
package org.cryptomator.frontend.webdav.mount;
abstract class AbstractWebDavMount implements WebDavMount {

View File

@@ -7,7 +7,7 @@
* Sebastian Stenzel - initial API and implementation
* Markus Kreusch - Refactored WebDavMounter to use strategy pattern
******************************************************************************/
package org.cryptomator.ui.util.mount;
package org.cryptomator.frontend.webdav.mount;
public class CommandFailedException extends Exception {

View File

@@ -6,12 +6,14 @@
* Contributors:
* Markus Kreusch - Refactored WebDavMounter to use strategy pattern
******************************************************************************/
package org.cryptomator.ui.util.mount;
package org.cryptomator.frontend.webdav.mount;
import java.net.URI;
import java.util.Map;
import java.util.Optional;
import org.cryptomator.frontend.Frontend.MountParam;
/**
* A WebDavMounter acting as fallback if no other mounter works.
*

View File

@@ -8,7 +8,7 @@
* Markus Kreusch - Refactored WebDavMounter to use strategy pattern
* Mohit Raju - Added fallback schema-name "webdav" when opening file managers
******************************************************************************/
package org.cryptomator.ui.util.mount;
package org.cryptomator.frontend.webdav.mount;
import java.net.URI;
import java.util.Map;
@@ -18,13 +18,15 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.ui.util.command.Script;
import org.cryptomator.frontend.Frontend.MountParam;
import org.cryptomator.frontend.webdav.mount.command.Script;
@Singleton
final class LinuxGvfsWebDavMounter implements WebDavMounterStrategy {
@Inject
LinuxGvfsWebDavMounter() {}
LinuxGvfsWebDavMounter() {
}
@Override
public boolean shouldWork() {
@@ -40,7 +42,7 @@ final class LinuxGvfsWebDavMounter implements WebDavMounterStrategy {
return false;
}
}
@Override
public void warmUp(int serverPort) {
// no-op
@@ -48,32 +50,29 @@ final class LinuxGvfsWebDavMounter implements WebDavMounterStrategy {
@Override
public WebDavMount mount(URI uri, Map<MountParam, Optional<String>> mountParams) throws CommandFailedException {
final Script mountScript = Script.fromLines(
"set -x",
"gvfs-mount \"dav:$DAV_SSP\"")
.addEnv("DAV_SSP", uri.getRawSchemeSpecificPart());
final Script mountScript = Script.fromLines("set -x", "gvfs-mount \"dav:$DAV_SSP\"").addEnv("DAV_SSP", uri.getRawSchemeSpecificPart());
mountScript.execute();
return new LinuxGvfsWebDavMount(uri);
}
private static class LinuxGvfsWebDavMount extends AbstractWebDavMount {
private final URI webDavUri;
private final Script testMountStillExistsScript;
private final Script unmountScript;
private LinuxGvfsWebDavMount(URI webDavUri) {
this.webDavUri = webDavUri;
this.testMountStillExistsScript = Script.fromLines("set -x", "test `gvfs-mount --list | grep \"$DAV_SSP\" | wc -l` -eq 1").addEnv("DAV_SSP", webDavUri.getRawSchemeSpecificPart());
this.unmountScript = Script.fromLines("set -x", "gvfs-mount -u \"dav:$DAV_SSP\"").addEnv("DAV_SSP", webDavUri.getRawSchemeSpecificPart());
}
@Override
public void unmount() throws CommandFailedException {
boolean mountStillExists;
try {
testMountStillExistsScript.execute();
mountStillExists = true;
} catch(CommandFailedException e) {
} catch (CommandFailedException e) {
mountStillExists = false;
}
// only attempt unmount if user didn't unmount manually:
@@ -85,16 +84,16 @@ final class LinuxGvfsWebDavMounter implements WebDavMounterStrategy {
@Override
public void reveal() throws CommandFailedException {
try {
openMountWithWebdavUri("dav:"+webDavUri.getRawSchemeSpecificPart()).execute();
openMountWithWebdavUri("dav:" + webDavUri.getRawSchemeSpecificPart()).execute();
} catch (CommandFailedException exception) {
openMountWithWebdavUri("webdav:"+webDavUri.getRawSchemeSpecificPart()).execute();
openMountWithWebdavUri("webdav:" + webDavUri.getRawSchemeSpecificPart()).execute();
}
}
private Script openMountWithWebdavUri(String webdavUri){
private Script openMountWithWebdavUri(String webdavUri) {
return Script.fromLines("set -x", "xdg-open \"$DAV_URI\"").addEnv("DAV_URI", webdavUri);
}
}
}

View File

@@ -7,7 +7,7 @@
* Sebastian Stenzel - initial API and implementation, strategy fine tuning
* Markus Kreusch - Refactored WebDavMounter to use strategy pattern
******************************************************************************/
package org.cryptomator.ui.util.mount;
package org.cryptomator.frontend.webdav.mount;
import java.net.URI;
import java.nio.file.FileSystems;
@@ -20,13 +20,15 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.ui.util.command.Script;
import org.cryptomator.frontend.Frontend.MountParam;
import org.cryptomator.frontend.webdav.mount.command.Script;
@Singleton
final class MacOsXWebDavMounter implements WebDavMounterStrategy {
@Inject
MacOsXWebDavMounter() {}
MacOsXWebDavMounter() {
}
@Override
public boolean shouldWork() {
@@ -43,31 +45,26 @@ final class MacOsXWebDavMounter implements WebDavMounterStrategy {
final String mountName = mountParams.get(MountParam.MOUNT_NAME).orElseThrow(() -> {
return new IllegalArgumentException("Missing mount parameter MOUNT_NAME.");
});
// we don't use the uri to derive a path, as it *could* be longer than 255 chars.
final String path = "/Volumes/Cryptomator_" + UUID.randomUUID().toString();
final Script mountScript = Script.fromLines(
"mkdir \"$MOUNT_PATH\"",
"mount_webdav -S -v $MOUNT_NAME \"$DAV_AUTHORITY$DAV_PATH\" \"$MOUNT_PATH\"")
.addEnv("DAV_AUTHORITY", uri.getRawAuthority())
.addEnv("DAV_PATH", uri.getRawPath())
.addEnv("MOUNT_PATH", path)
.addEnv("MOUNT_NAME", mountName);
final Script mountScript = Script.fromLines("mkdir \"$MOUNT_PATH\"", "mount_webdav -S -v $MOUNT_NAME \"$DAV_AUTHORITY$DAV_PATH\" \"$MOUNT_PATH\"").addEnv("DAV_AUTHORITY", uri.getRawAuthority())
.addEnv("DAV_PATH", uri.getRawPath()).addEnv("MOUNT_PATH", path).addEnv("MOUNT_NAME", mountName);
mountScript.execute();
return new MacWebDavMount(path);
}
private static class MacWebDavMount extends AbstractWebDavMount {
private final String mountPath;
private final Script revealScript;
private final Script unmountScript;
private MacWebDavMount(String mountPath) {
this.mountPath = mountPath;
this.revealScript = Script.fromLines("open \"$MOUNT_PATH\"").addEnv("MOUNT_PATH", mountPath);
this.unmountScript = Script.fromLines("diskutil umount $MOUNT_PATH").addEnv("MOUNT_PATH", mountPath);
}
@Override
public void unmount() throws CommandFailedException {
// only attempt unmount if user didn't unmount manually:
@@ -80,7 +77,7 @@ final class MacOsXWebDavMounter implements WebDavMounterStrategy {
public void reveal() throws CommandFailedException {
revealScript.execute();
}
}
}

View File

@@ -1,4 +1,4 @@
package org.cryptomator.ui.util.mount;
package org.cryptomator.frontend.webdav.mount;
import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableList;

View File

@@ -6,7 +6,7 @@
* Contributors:
* Markus Kreusch - Refactored WebDavMounter to use strategy pattern
******************************************************************************/
package org.cryptomator.ui.util.mount;
package org.cryptomator.frontend.webdav.mount;
/**
* A mounted webdav share.

View File

@@ -7,15 +7,15 @@
* Sebastian Stenzel - initial API and implementation
* Markus Kreusch - Refactored to use strategy pattern
******************************************************************************/
package org.cryptomator.ui.util.mount;
package org.cryptomator.frontend.webdav.mount;
import java.net.URI;
import java.util.Map;
import java.util.Optional;
import org.cryptomator.frontend.Frontend.MountParam;
public interface WebDavMounter {
public static enum MountParam {MOUNT_NAME, WIN_DRIVE_LETTER}
/**
* Tries to mount a given webdav share.

View File

@@ -7,16 +7,14 @@
* Markus Kreusch - Refactored to use strategy pattern
* Sebastian Stenzel - Refactored to use Guice provider, added warmup-phase for windows mounts.
******************************************************************************/
package org.cryptomator.ui.util.mount;
package org.cryptomator.frontend.webdav.mount;
import java.util.Collection;
import java.util.concurrent.ExecutorService;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import org.cryptomator.webdav.WebDavServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -27,15 +25,12 @@ public class WebDavMounterProvider implements Provider<WebDavMounter> {
private final WebDavMounterStrategy choosenStrategy;
@Inject
public WebDavMounterProvider(WebDavServer server, ExecutorService executorService, MountStrategies availableStrategies) {
public WebDavMounterProvider(MountStrategies availableStrategies) {
this.choosenStrategy = getStrategyWhichShouldWork(availableStrategies);
executorService.execute(() -> {
this.choosenStrategy.warmUp(server.getPort());
});
}
@Override
public WebDavMounterStrategy get() {
public WebDavMounter get() {
return this.choosenStrategy;
}

View File

@@ -7,7 +7,7 @@
* Markus Kreusch - Refactored WebDavMounter to use strategy pattern
* Sebastian Stenzel - minor strategy fine tuning
******************************************************************************/
package org.cryptomator.ui.util.mount;
package org.cryptomator.frontend.webdav.mount;
/**
* A strategy able to mount a webdav share and display it to the user.

View File

@@ -1,4 +1,4 @@
package org.cryptomator.ui.util.mount;
package org.cryptomator.frontend.webdav.mount;
import static java.util.stream.Collectors.toSet;
import static java.util.stream.IntStream.rangeClosed;

View File

@@ -7,9 +7,9 @@
* Sebastian Stenzel - initial API and implementation, strategy fine tuning
* Markus Kreusch - Refactored WebDavMounter to use strategy pattern
******************************************************************************/
package org.cryptomator.ui.util.mount;
package org.cryptomator.frontend.webdav.mount;
import static org.cryptomator.ui.util.command.Script.fromLines;
import static org.cryptomator.frontend.webdav.mount.command.Script.fromLines;
import java.net.URI;
import java.util.Map;
@@ -23,8 +23,9 @@ import javax.inject.Singleton;
import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.ui.util.command.CommandResult;
import org.cryptomator.ui.util.command.Script;
import org.cryptomator.frontend.Frontend.MountParam;
import org.cryptomator.frontend.webdav.mount.command.CommandResult;
import org.cryptomator.frontend.webdav.mount.command.Script;
/**
* A {@link WebDavMounterStrategy} utilizing the "net use" command.
@@ -38,7 +39,7 @@ final class WindowsWebDavMounter implements WebDavMounterStrategy {
private static final int MAX_MOUNT_ATTEMPTS = 8;
private static final char AUTO_ASSIGN_DRIVE_LETTER = '*';
private final WindowsDriveLetters driveLetters;
@Inject
WindowsWebDavMounter(WindowsDriveLetters driveLetters) {
this.driveLetters = driveLetters;
@@ -60,7 +61,7 @@ final class WindowsWebDavMounter implements WebDavMounterStrategy {
if (driveLetters.getOccupiedDriveLetters().contains(driveLetter)) {
throw new CommandFailedException("Drive letter occupied.");
}
final String driveLetterStr = driveLetter.charValue() == AUTO_ASSIGN_DRIVE_LETTER ? CharUtils.toString(AUTO_ASSIGN_DRIVE_LETTER) : driveLetter + ":";
final Script localhostMountScript = fromLines("net use %DRIVE_LETTER% \\\\localhost@%DAV_PORT%\\DavWWWRoot%DAV_UNC_PATH% /persistent:no");
localhostMountScript.addEnv("DRIVE_LETTER", driveLetterStr);
@@ -74,13 +75,14 @@ final class WindowsWebDavMounter implements WebDavMounterStrategy {
ipv6literaltMountScript.addEnv("DRIVE_LETTER", driveLetterStr);
ipv6literaltMountScript.addEnv("DAV_PORT", String.valueOf(uri.getPort()));
ipv6literaltMountScript.addEnv("DAV_UNC_PATH", uri.getRawPath().replace('/', '\\'));
final Script proxyBypassScript = fromLines("reg add \"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\" /v \"ProxyOverride\" /d \"<local>;0--1.ipv6-literal.net;0--1.ipv6-literal.net:%DAV_PORT%\" /f");
final Script proxyBypassScript = fromLines(
"reg add \"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\" /v \"ProxyOverride\" /d \"<local>;0--1.ipv6-literal.net;0--1.ipv6-literal.net:%DAV_PORT%\" /f");
proxyBypassScript.addEnv("DAV_PORT", String.valueOf(uri.getPort()));
mountResult = bypassProxyAndRetryMount(localhostMountScript, ipv6literaltMountScript, proxyBypassScript);
}
return new WindowsWebDavMount(driveLetter.charValue() == AUTO_ASSIGN_DRIVE_LETTER ? getDriveLetter(mountResult.getStdOut()) : driveLetter);
}
private CommandResult bypassProxyAndRetryMount(Script localhostMountScript, Script ipv6literalMountScript, Script proxyBypassScript) throws CommandFailedException {
CommandFailedException latestException = null;
for (int i = 0; i < MAX_MOUNT_ATTEMPTS; i++) {
@@ -109,18 +111,18 @@ final class WindowsWebDavMounter implements WebDavMounterStrategy {
throw new CommandFailedException("Failed to get a drive letter from net use output.");
}
}
private class WindowsWebDavMount extends AbstractWebDavMount {
private final Character driveLetter;
private final Script openExplorerScript;
private final Script unmountScript;
private WindowsWebDavMount(Character driveLetter) {
this.driveLetter = driveLetter;
this.openExplorerScript = fromLines("start explorer.exe " + driveLetter + ":");
this.unmountScript = fromLines("net use " + driveLetter + ": /delete").addEnv("DRIVE_LETTER", Character.toString(driveLetter));
}
@Override
public void unmount() throws CommandFailedException {
// only attempt unmount if user didn't unmount manually:
@@ -133,7 +135,7 @@ final class WindowsWebDavMounter implements WebDavMounterStrategy {
public void reveal() throws CommandFailedException {
openExplorerScript.execute();
}
private boolean isVolumeMounted() {
return driveLetters.getOccupiedDriveLetters().contains(driveLetter);
}

View File

@@ -7,7 +7,7 @@
* Markus Kreusch
* Sebastian Stenzel - using Futures, lazy loading for out/err.
******************************************************************************/
package org.cryptomator.ui.util.command;
package org.cryptomator.frontend.webdav.mount.command;
import static java.lang.String.format;
@@ -15,7 +15,7 @@ import java.io.IOException;
import org.apache.commons.io.IOUtils;
import org.apache.logging.log4j.util.Strings;
import org.cryptomator.ui.util.mount.CommandFailedException;
import org.cryptomator.frontend.webdav.mount.CommandFailedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -7,7 +7,7 @@
* Markus Kreusch
* Sebastian Stenzel - Refactoring
******************************************************************************/
package org.cryptomator.ui.util.command;
package org.cryptomator.frontend.webdav.mount.command;
import static java.lang.String.format;
import static org.apache.commons.lang3.SystemUtils.IS_OS_UNIX;
@@ -24,7 +24,7 @@ import java.util.stream.Collectors;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.ui.util.mount.CommandFailedException;
import org.cryptomator.frontend.webdav.mount.CommandFailedException;
/**
* <p>

View File

@@ -6,7 +6,7 @@
* Contributors:
* Sebastian Stenzel
******************************************************************************/
package org.cryptomator.ui.util.command;
package org.cryptomator.frontend.webdav.mount.command;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
@@ -17,7 +17,7 @@ import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.cryptomator.ui.util.mount.CommandFailedException;
import org.cryptomator.frontend.webdav.mount.CommandFailedException;
final class FutureCommandResult implements Future<CommandResult>, Runnable {

View File

@@ -6,13 +6,13 @@
* Contributors:
* Markus Kreusch
******************************************************************************/
package org.cryptomator.ui.util.command;
package org.cryptomator.frontend.webdav.mount.command;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.cryptomator.ui.util.mount.CommandFailedException;
import org.cryptomator.frontend.webdav.mount.CommandFailedException;
public final class Script {

View File

@@ -6,8 +6,9 @@
* Contributors:
* Sebastian Stenzel - initial API and implementation
*******************************************************************************/
package org.cryptomator.webdav.server;
package org.cryptomator.frontend.webdav;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.EnumSet;
@@ -27,7 +28,7 @@ public class InMemoryWebDavServer {
server.start();
FileSystem fileSystem = setupFilesystem();
ServletContextHandler servlet = server.addServlet(fileSystem, "/foo");
ServletContextHandler servlet = server.addServlet(fileSystem, URI.create("http://localhost:8080/foo"));
servlet.addFilter(LoggingHttpFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
servlet.start();

View File

@@ -6,12 +6,13 @@
* Contributors:
* Sebastian Stenzel - initial API and implementation
*******************************************************************************/
package org.cryptomator.webdav.server;
package org.cryptomator.frontend.webdav;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -36,7 +37,7 @@ public class NioWebDavServer {
server.start();
FileSystem fileSystem = setupFilesystem();
ServletContextHandler servlet = server.addServlet(fileSystem, "/foo");
ServletContextHandler servlet = server.addServlet(fileSystem, URI.create("http://localhost:8080/foo"));
servlet.addFilter(LoggingHttpFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
servlet.start();

View File

@@ -1,9 +1,10 @@
package org.cryptomator.webdav.server;
package org.cryptomator.frontend.webdav;
import static org.hamcrest.collection.IsArrayContaining.hasItemInArray;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
@@ -49,7 +50,7 @@ public class WebDavServerTest {
@Before
public void startServlet() throws Exception {
fs = new InMemoryFileSystem();
servlet = SERVER.addServlet(fs, "/test");
servlet = SERVER.addServlet(fs, URI.create("http://localhost:" + SERVER.getPort() + "/test"));
servlet.start();
servletRoot = "http://localhost:" + SERVER.getPort() + "/test";
}

View File

@@ -52,19 +52,18 @@
<dependencyManagement>
<dependencies>
<!-- modules -->
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>commons-test</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>filesystem-api</artifactId>
@@ -91,22 +90,18 @@
<artifactId>filesystem-crypto</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>frontend-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>frontend-webdav</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>crypto-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>crypto-aes</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>ui</artifactId>
@@ -259,10 +254,8 @@
<module>filesystem-nameshortening</module>
<module>filesystem-crypto</module>
<module>filesystem-invariants-tests</module>
<module>frontend-api</module>
<module>frontend-webdav</module>
<module>crypto-api</module>
<module>crypto-aes</module>
<module>core</module>
<module>ui</module>
</modules>

View File

@@ -20,11 +20,23 @@
<dependencies>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>core</artifactId>
<artifactId>filesystem-api</artifactId>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>crypto-aes</artifactId>
<artifactId>filesystem-nio</artifactId>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>filesystem-crypto</artifactId>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>frontend-api</artifactId>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>frontend-webdav</artifactId>
</dependency>
<!-- JSON -->

View File

@@ -7,18 +7,17 @@ import java.util.concurrent.Executors;
import javax.inject.Named;
import javax.inject.Singleton;
import org.cryptomator.crypto.Cryptor;
import org.cryptomator.crypto.SamplingCryptorDecorator;
import org.cryptomator.crypto.aes256.CryptoModule;
import org.cryptomator.filesystem.crypto.CryptoFileSystemModule;
import org.cryptomator.frontend.FrontendFactory;
import org.cryptomator.frontend.webdav.WebDavServer;
import org.cryptomator.frontend.webdav.mount.WebDavMounter;
import org.cryptomator.frontend.webdav.mount.WebDavMounterProvider;
import org.cryptomator.ui.model.VaultObjectMapperProvider;
import org.cryptomator.ui.settings.Settings;
import org.cryptomator.ui.settings.SettingsProvider;
import org.cryptomator.ui.util.DeferredCloser;
import org.cryptomator.ui.util.DeferredCloser.Closer;
import org.cryptomator.ui.util.SemVerComparator;
import org.cryptomator.ui.util.mount.WebDavMounter;
import org.cryptomator.ui.util.mount.WebDavMounterProvider;
import org.cryptomator.webdav.WebDavServer;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -26,7 +25,7 @@ import dagger.Module;
import dagger.Provides;
import javafx.application.Application;
@Module(includes = CryptoModule.class)
@Module(includes = CryptoFileSystemModule.class)
class CryptomatorModule {
private final Application application;
@@ -83,18 +82,11 @@ class CryptomatorModule {
@Provides
@Singleton
WebDavServer provideWebDavServer() {
final WebDavServer webDavServer = new WebDavServer();
FrontendFactory provideFrontendFactory(WebDavServer webDavServer) {
webDavServer.start();
return closeLater(webDavServer, WebDavServer::stop);
}
@Provides
@Named("SamplingCryptor")
Cryptor provideCryptor(Cryptor cryptor) {
return SamplingCryptorDecorator.decorate(cryptor);
}
private <T> T closeLater(T object, Closer<T> closer) {
return deferredCloser.closeLater(object, closer).get().get();
}

View File

@@ -1,28 +1,17 @@
package org.cryptomator.ui.controllers;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.ResourceBundle;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
import org.cryptomator.crypto.exceptions.UnsupportedVaultException;
import org.cryptomator.crypto.exceptions.WrongPasswordException;
import org.cryptomator.ui.controls.SecPasswordField;
import org.cryptomator.ui.model.Vault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
@@ -107,75 +96,80 @@ public class ChangePasswordController extends AbstractFXMLViewController {
@FXML
private void didClickChangePasswordButton(ActionEvent event) {
downloadsPageLink.setVisible(false);
final Path masterKeyPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_FILE);
final Path masterKeyBackupPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_BACKUP_FILE);
// decrypt with old password:
final CharSequence oldPassword = oldPasswordField.getCharacters();
try (final InputStream masterKeyInputStream = Files.newInputStream(masterKeyPath, StandardOpenOption.READ)) {
vault.getCryptor().decryptMasterKey(masterKeyInputStream, oldPassword);
Files.copy(masterKeyPath, masterKeyBackupPath, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException ex) {
messageText.setText(resourceBundle.getString("changePassword.errorMessage.decryptionFailed"));
LOG.error("Decryption failed for technical reasons.", ex);
newPasswordField.swipe();
retypePasswordField.swipe();
return;
} catch (WrongPasswordException e) {
messageText.setText(resourceBundle.getString("changePassword.errorMessage.wrongPassword"));
newPasswordField.swipe();
retypePasswordField.swipe();
Platform.runLater(oldPasswordField::requestFocus);
return;
} catch (UnsupportedKeyLengthException ex) {
messageText.setText(resourceBundle.getString("changePassword.errorMessage.unsupportedKeyLengthInstallJCE"));
LOG.warn("Unsupported Key-Length. Please install Oracle Java Cryptography Extension (JCE).", ex);
newPasswordField.swipe();
retypePasswordField.swipe();
return;
} catch (UnsupportedVaultException e) {
downloadsPageLink.setVisible(true);
if (e.isVaultOlderThanSoftware()) {
messageText.setText(resourceBundle.getString("changePassword.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " ");
} else if (e.isSoftwareOlderThanVault()) {
messageText.setText(resourceBundle.getString("changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault") + " ");
}
newPasswordField.swipe();
retypePasswordField.swipe();
return;
} finally {
oldPasswordField.swipe();
}
// when we reach this line, decryption was successful.
// encrypt with new password:
final CharSequence newPassword = newPasswordField.getCharacters();
try (final OutputStream masterKeyOutputStream = Files.newOutputStream(masterKeyPath, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.SYNC)) {
vault.getCryptor().encryptMasterKey(masterKeyOutputStream, newPassword);
messageText.setText(resourceBundle.getString("changePassword.infoMessage.success"));
Platform.runLater(this::didChangePassword);
// At this point the backup is still using the old password.
// It will be changed as soon as the user unlocks the vault the next time.
// This way he can still restore the old password, if he doesn't remember the new one.
} catch (IOException ex) {
LOG.error("Re-encryption failed for technical reasons. Restoring Backup.", ex);
this.restoreBackupQuietly();
} finally {
newPasswordField.swipe();
retypePasswordField.swipe();
}
throw new UnsupportedOperationException("TODO");
/*
* downloadsPageLink.setVisible(false);
* final Path masterKeyPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_FILE);
* final Path masterKeyBackupPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_BACKUP_FILE);
*
* // decrypt with old password:
* final CharSequence oldPassword = oldPasswordField.getCharacters();
* try (final InputStream masterKeyInputStream = Files.newInputStream(masterKeyPath, StandardOpenOption.READ)) {
* vault.getCryptor().decryptMasterKey(masterKeyInputStream, oldPassword);
* Files.copy(masterKeyPath, masterKeyBackupPath, StandardCopyOption.REPLACE_EXISTING);
* } catch (IOException ex) {
* messageText.setText(resourceBundle.getString("changePassword.errorMessage.decryptionFailed"));
* LOG.error("Decryption failed for technical reasons.", ex);
* newPasswordField.swipe();
* retypePasswordField.swipe();
* return;
* } catch (WrongPasswordException e) {
* messageText.setText(resourceBundle.getString("changePassword.errorMessage.wrongPassword"));
* newPasswordField.swipe();
* retypePasswordField.swipe();
* Platform.runLater(oldPasswordField::requestFocus);
* return;
* } catch (UnsupportedKeyLengthException ex) {
* messageText.setText(resourceBundle.getString("changePassword.errorMessage.unsupportedKeyLengthInstallJCE"));
* LOG.warn("Unsupported Key-Length. Please install Oracle Java Cryptography Extension (JCE).", ex);
* newPasswordField.swipe();
* retypePasswordField.swipe();
* return;
* } catch (UnsupportedVaultException e) {
* downloadsPageLink.setVisible(true);
* if (e.isVaultOlderThanSoftware()) {
* messageText.setText(resourceBundle.getString("changePassword.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " ");
* } else if (e.isSoftwareOlderThanVault()) {
* messageText.setText(resourceBundle.getString("changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault") + " ");
* }
* newPasswordField.swipe();
* retypePasswordField.swipe();
* return;
* } finally {
* oldPasswordField.swipe();
* }
*
* // when we reach this line, decryption was successful.
*
* // encrypt with new password:
* final CharSequence newPassword = newPasswordField.getCharacters();
* try (final OutputStream masterKeyOutputStream = Files.newOutputStream(masterKeyPath, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.SYNC)) {
* vault.getCryptor().encryptMasterKey(masterKeyOutputStream, newPassword);
* messageText.setText(resourceBundle.getString("changePassword.infoMessage.success"));
* Platform.runLater(this::didChangePassword);
* // At this point the backup is still using the old password.
* // It will be changed as soon as the user unlocks the vault the next time.
* // This way he can still restore the old password, if he doesn't remember the new one.
* } catch (IOException ex) {
* LOG.error("Re-encryption failed for technical reasons. Restoring Backup.", ex);
* this.restoreBackupQuietly();
* } finally {
* newPasswordField.swipe();
* retypePasswordField.swipe();
* }
*/
}
private void restoreBackupQuietly() {
final Path masterKeyPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_FILE);
final Path masterKeyBackupPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_BACKUP_FILE);
try {
Files.copy(masterKeyBackupPath, masterKeyPath, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException ex) {
LOG.error("Restoring Backup failed.", ex);
}
/*
* final Path masterKeyPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_FILE);
* final Path masterKeyBackupPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_BACKUP_FILE);
* try {
* Files.copy(masterKeyBackupPath, masterKeyPath, StandardCopyOption.REPLACE_EXISTING);
* } catch (IOException ex) {
* LOG.error("Restoring Backup failed.", ex);
* }
*/
}
private void didChangePassword() {

View File

@@ -9,14 +9,8 @@
package org.cryptomator.ui.controllers;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ResourceBundle;
import javax.inject.Inject;
@@ -90,23 +84,11 @@ public class InitializeController extends AbstractFXMLViewController {
@FXML
protected void initializeVault(ActionEvent event) {
setControlsDisabled(true);
final Path masterKeyPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_FILE);
final CharSequence password = passwordField.getCharacters();
try (OutputStream masterKeyOutputStream = Files.newOutputStream(masterKeyPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW)) {
vault.getCryptor().randomizeMasterKey();
vault.getCryptor().encryptMasterKey(masterKeyOutputStream, password);
final String dataRootDir = vault.getCryptor().encryptDirectoryPath("", FileSystems.getDefault().getSeparator());
final Path dataRootPath = vault.getPath().resolve("d").resolve(dataRootDir);
final Path metadataPath = vault.getPath().resolve("m");
Files.createDirectories(dataRootPath);
Files.createDirectories(metadataPath);
if (listener != null) {
listener.didInitialize(this);
}
final CharSequence passphrase = passwordField.getCharacters();
try {
vault.create(passphrase);
} catch (FileAlreadyExistsException ex) {
messageLabel.setText(resourceBundle.getString("initialize.messageLabel.alreadyInitialized"));
} catch (InvalidPathException ex) {
messageLabel.setText(resourceBundle.getString("initialize.messageLabel.invalidPath"));
} catch (IOException ex) {
LOG.error("I/O Exception", ex);
} finally {

View File

@@ -8,31 +8,22 @@
******************************************************************************/
package org.cryptomator.ui.controllers;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.Comparator;
import java.util.ResourceBundle;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import javax.inject.Inject;
import javax.security.auth.DestroyFailedException;
import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
import org.cryptomator.crypto.exceptions.UnsupportedVaultException;
import org.cryptomator.crypto.exceptions.WrongPasswordException;
import org.cryptomator.crypto.engine.InvalidPassphraseException;
import org.cryptomator.frontend.FrontendCreationFailedException;
import org.cryptomator.frontend.webdav.mount.WindowsDriveLetters;
import org.cryptomator.ui.controls.SecPasswordField;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.util.FXThreads;
import org.cryptomator.ui.util.mount.CommandFailedException;
import org.cryptomator.ui.util.mount.WindowsDriveLetters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -258,49 +249,30 @@ public class UnlockController extends AbstractFXMLViewController {
setControlsDisabled(true);
progressIndicator.setVisible(true);
downloadsPageLink.setVisible(false);
final Path masterKeyPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_FILE);
final Path masterKeyBackupPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_BACKUP_FILE);
final CharSequence password = passwordField.getCharacters();
try (final InputStream masterKeyInputStream = Files.newInputStream(masterKeyPath, StandardOpenOption.READ)) {
vault.getCryptor().decryptMasterKey(masterKeyInputStream, password);
if (!vault.startServer()) {
messageText.setText(resourceBundle.getString("unlock.messageLabel.startServerFailed"));
vault.getCryptor().destroy();
return;
}
// at this point we know for sure, that the masterkey can be decrypted, so lets make a backup:
Files.copy(masterKeyPath, masterKeyBackupPath, StandardCopyOption.REPLACE_EXISTING);
vault.setUnlocked(true);
final Future<Boolean> futureMount = exec.submit(vault::mount);
try {
vault.activateFrontend(password);
Future<Boolean> futureMount = exec.submit(vault::mount);
FXThreads.runOnMainThreadWhenFinished(exec, futureMount, this::unlockAndMountFinished);
} catch (IOException ex) {
setControlsDisabled(false);
progressIndicator.setVisible(false);
messageText.setText(resourceBundle.getString("unlock.errorMessage.decryptionFailed"));
LOG.error("Decryption failed for technical reasons.", ex);
} catch (WrongPasswordException e) {
} catch (InvalidPassphraseException e) {
setControlsDisabled(false);
progressIndicator.setVisible(false);
messageText.setText(resourceBundle.getString("unlock.errorMessage.wrongPassword"));
Platform.runLater(passwordField::requestFocus);
} catch (UnsupportedKeyLengthException ex) {
} catch (FrontendCreationFailedException ex) {
setControlsDisabled(false);
progressIndicator.setVisible(false);
messageText.setText(resourceBundle.getString("unlock.errorMessage.unsupportedKeyLengthInstallJCE"));
LOG.warn("Unsupported Key-Length. Please install Oracle Java Cryptography Extension (JCE).", ex);
} catch (UnsupportedVaultException e) {
setControlsDisabled(false);
progressIndicator.setVisible(false);
downloadsPageLink.setVisible(true);
if (e.isVaultOlderThanSoftware()) {
messageText.setText(resourceBundle.getString("unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " ");
} else if (e.isSoftwareOlderThanVault()) {
messageText.setText(resourceBundle.getString("unlock.errorMessage.unsupportedVersion.softwareOlderThanVault") + " ");
}
} catch (DestroyFailedException e) {
setControlsDisabled(false);
progressIndicator.setVisible(false);
LOG.error("Destruction of cryptor threw an exception.", e);
messageText.setText(resourceBundle.getString("unlock.errorMessage.decryptionFailed"));
LOG.error("Decryption failed for technical reasons.", ex);
// } catch (UnsupportedVaultException e) {
// setControlsDisabled(false);
// progressIndicator.setVisible(false);
// downloadsPageLink.setVisible(true);
// if (e.isVaultOlderThanSoftware()) {
// messageText.setText(resourceBundle.getString("unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " ");
// } else if (e.isSoftwareOlderThanVault()) {
// messageText.setText(resourceBundle.getString("unlock.errorMessage.unsupportedVersion.softwareOlderThanVault") + " ");
// }
} finally {
passwordField.swipe();
}
@@ -317,18 +289,9 @@ public class UnlockController extends AbstractFXMLViewController {
progressIndicator.setVisible(false);
setControlsDisabled(false);
if (vault.isUnlocked() && !mountSuccess) {
exec.submit(() -> {
vault.stopServer();
vault.setUnlocked(false);
});
exec.submit(vault::deactivateFrontend);
} else if (vault.isUnlocked() && mountSuccess) {
exec.submit(() -> {
try {
vault.reveal();
} catch (CommandFailedException e) {
LOG.error("Failed to reveal mounted vault", e);
}
});
exec.submit(vault::reveal);
}
if (mountSuccess && listener != null) {
listener.didUnlock(this);

View File

@@ -15,10 +15,8 @@ import java.util.concurrent.ExecutorService;
import javax.inject.Inject;
import javax.inject.Provider;
import org.cryptomator.crypto.CryptorIOSampling;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.util.ActiveWindowStyleSupport;
import org.cryptomator.ui.util.mount.CommandFailedException;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
@@ -30,7 +28,6 @@ import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.Label;
import javafx.stage.Stage;
@@ -81,30 +78,21 @@ public class UnlockedController extends AbstractFXMLViewController {
@FXML
private void didClickRevealVault(ActionEvent event) {
exec.submit(() -> {
try {
vault.reveal();
} catch (CommandFailedException e) {
Platform.runLater(() -> {
messageLabel.setText(resourceBundle.getString("unlocked.label.revealFailed"));
});
}
});
exec.submit(vault::reveal);
}
@FXML
private void didClickCloseVault(ActionEvent event) {
exec.submit(() -> {
try {
vault.unmount();
} catch (CommandFailedException e) {
Platform.runLater(() -> {
messageLabel.setText(resourceBundle.getString("unlocked.label.unmountFailed"));
});
return;
}
vault.stopServer();
vault.setUnlocked(false);
// try {
vault.unmount();
// } catch (CommandFailedException e) {
// Platform.runLater(() -> {
// messageLabel.setText(resourceBundle.getString("unlocked.label.unmountFailed"));
// });
// return;
// }
vault.deactivateFrontend();
if (listener != null) {
Platform.runLater(() -> {
listener.didLock(this);
@@ -129,7 +117,7 @@ public class UnlockedController extends AbstractFXMLViewController {
// IO Graph
// ****************************************
private void startIoSampling(final CryptorIOSampling sampler) {
private void startIoSampling(final Object sampler) {
final Series<Number, Number> decryptedBytes = new Series<>();
decryptedBytes.setName("decrypted");
final Series<Number, Number> encryptedBytes = new Series<>();
@@ -156,14 +144,14 @@ public class UnlockedController extends AbstractFXMLViewController {
private static final double BYTES_TO_MEGABYTES_FACTOR = 1.0 / IO_SAMPLING_INTERVAL / 1024.0 / 1024.0;
private static final double SMOOTHING_FACTOR = 0.3;
private static final long EFFECTIVELY_ZERO = 100000; // 100kb
private final CryptorIOSampling sampler;
private final Object sampler;
private final Series<Number, Number> decryptedBytes;
private final Series<Number, Number> encryptedBytes;
private int step = 0;
private long oldDecBytes = 0;
private long oldEncBytes = 0;
private final int step = 0;
private final long oldDecBytes = 0;
private final long oldEncBytes = 0;
public IoSamplingAnimationHandler(CryptorIOSampling sampler, Series<Number, Number> decryptedBytes, Series<Number, Number> encryptedBytes) {
public IoSamplingAnimationHandler(Object sampler, Series<Number, Number> decryptedBytes, Series<Number, Number> encryptedBytes) {
this.sampler = sampler;
this.decryptedBytes = decryptedBytes;
this.encryptedBytes = encryptedBytes;
@@ -171,28 +159,30 @@ public class UnlockedController extends AbstractFXMLViewController {
@Override
public void handle(ActionEvent event) {
step++;
final long decBytes = sampler.pollDecryptedBytes(true);
final double smoothedDecBytes = oldDecBytes + SMOOTHING_FACTOR * (decBytes - oldDecBytes);
final double smoothedDecMb = smoothedDecBytes * BYTES_TO_MEGABYTES_FACTOR;
oldDecBytes = smoothedDecBytes > EFFECTIVELY_ZERO ? (long) smoothedDecBytes : 0l;
decryptedBytes.getData().add(new Data<Number, Number>(step, smoothedDecMb));
if (decryptedBytes.getData().size() > IO_SAMPLING_STEPS) {
decryptedBytes.getData().remove(0);
}
final long encBytes = sampler.pollEncryptedBytes(true);
final double smoothedEncBytes = oldEncBytes + SMOOTHING_FACTOR * (encBytes - oldEncBytes);
final double smoothedEncMb = smoothedEncBytes * BYTES_TO_MEGABYTES_FACTOR;
oldEncBytes = smoothedEncBytes > EFFECTIVELY_ZERO ? (long) smoothedEncBytes : 0l;
encryptedBytes.getData().add(new Data<Number, Number>(step, smoothedEncMb));
if (encryptedBytes.getData().size() > IO_SAMPLING_STEPS) {
encryptedBytes.getData().remove(0);
}
xAxis.setLowerBound(step - IO_SAMPLING_STEPS);
xAxis.setUpperBound(step);
/*
* step++;
*
* final long decBytes = sampler.pollDecryptedBytes(true);
* final double smoothedDecBytes = oldDecBytes + SMOOTHING_FACTOR * (decBytes - oldDecBytes);
* final double smoothedDecMb = smoothedDecBytes * BYTES_TO_MEGABYTES_FACTOR;
* oldDecBytes = smoothedDecBytes > EFFECTIVELY_ZERO ? (long) smoothedDecBytes : 0l;
* decryptedBytes.getData().add(new Data<Number, Number>(step, smoothedDecMb));
* if (decryptedBytes.getData().size() > IO_SAMPLING_STEPS) {
* decryptedBytes.getData().remove(0);
* }
*
* final long encBytes = sampler.pollEncryptedBytes(true);
* final double smoothedEncBytes = oldEncBytes + SMOOTHING_FACTOR * (encBytes - oldEncBytes);
* final double smoothedEncMb = smoothedEncBytes * BYTES_TO_MEGABYTES_FACTOR;
* oldEncBytes = smoothedEncBytes > EFFECTIVELY_ZERO ? (long) smoothedEncBytes : 0l;
* encryptedBytes.getData().add(new Data<Number, Number>(step, smoothedEncMb));
* if (encryptedBytes.getData().size() > IO_SAMPLING_STEPS) {
* encryptedBytes.getData().remove(0);
* }
*
* xAxis.setLowerBound(step - IO_SAMPLING_STEPS);
* xAxis.setUpperBound(step);
*/
}
}
@@ -216,12 +206,14 @@ public class UnlockedController extends AbstractFXMLViewController {
});
// sample crypto-throughput:
stopIoSampling();
if (vault.getCryptor() instanceof CryptorIOSampling) {
startIoSampling((CryptorIOSampling) vault.getCryptor());
} else {
ioGraph.setVisible(false);
}
/*
* stopIoSampling();
* if (vault.getCryptor() instanceof CryptorIOSampling) {
* startIoSampling((CryptorIOSampling) vault.getCryptor());
* } else {
* ioGraph.setVisible(false);
* }
*/
}
public LockListener getListener() {

View File

@@ -2,6 +2,8 @@ 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.Files;
import java.nio.file.Path;
import java.text.Normalizer;
@@ -11,62 +13,58 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.security.auth.DestroyFailedException;
import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.StringUtils;
import org.cryptomator.crypto.Cryptor;
import org.cryptomator.filesystem.FileSystem;
import org.cryptomator.filesystem.crypto.CryptoFileSystemDelegate;
import org.cryptomator.filesystem.crypto.CryptoFileSystemFactory;
import org.cryptomator.filesystem.nio.NioFileSystem;
import org.cryptomator.frontend.Frontend;
import org.cryptomator.frontend.Frontend.MountParam;
import org.cryptomator.frontend.FrontendCreationFailedException;
import org.cryptomator.frontend.FrontendFactory;
import org.cryptomator.ui.util.DeferredClosable;
import org.cryptomator.ui.util.DeferredCloser;
import org.cryptomator.ui.util.FXThreads;
import org.cryptomator.ui.util.mount.CommandFailedException;
import org.cryptomator.ui.util.mount.WebDavMount;
import org.cryptomator.ui.util.mount.WebDavMounter;
import org.cryptomator.ui.util.mount.WebDavMounter.MountParam;
import org.cryptomator.webdav.WebDavServer;
import org.cryptomator.webdav.WebDavServer.ServletLifeCycleAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableMap;
import dagger.Lazy;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class Vault implements Serializable {
public class Vault implements Serializable, CryptoFileSystemDelegate {
private static final long serialVersionUID = 3754487289683599469L;
private static final Logger LOG = LoggerFactory.getLogger(Vault.class);
@Deprecated
public static final String VAULT_FILE_EXTENSION = ".cryptomator";
@Deprecated
public static final String VAULT_MASTERKEY_FILE = "masterkey.cryptomator";
public static final String VAULT_MASTERKEY_BACKUP_FILE = "masterkey.cryptomator.bkup";
private final Path path;
private final WebDavServer server;
private final Cryptor cryptor;
private final WebDavMounter mounter;
private final Lazy<FrontendFactory> frontendFactory;
private final DeferredCloser closer;
private final CryptoFileSystemFactory cryptoFileSystemFactory;
private final ObjectProperty<Boolean> unlocked = new SimpleObjectProperty<Boolean>(this, "unlocked", Boolean.FALSE);
private final ObservableList<String> namesOfResourcesWithInvalidMac = FXThreads.observableListOnMainThread(FXCollections.observableArrayList());
private final Set<String> whitelistedResourcesWithInvalidMac = new HashSet<>();
private String mountName;
private Character winDriveLetter;
private DeferredClosable<ServletLifeCycleAdapter> webDavServlet = DeferredClosable.empty();
private DeferredClosable<WebDavMount> webDavMount = DeferredClosable.empty();
private DeferredClosable<Frontend> filesystemFrontend = DeferredClosable.empty();
/**
* Package private constructor, use {@link VaultFactory}.
*/
Vault(final Path vaultDirectoryPath, final WebDavServer server, final Cryptor cryptor, final WebDavMounter mounter, final DeferredCloser closer) {
Vault(Path vaultDirectoryPath, Lazy<FrontendFactory> frontendFactory, CryptoFileSystemFactory cryptoFileSystemFactory, DeferredCloser closer) {
this.path = vaultDirectoryPath;
this.server = server;
this.cryptor = cryptor;
this.mounter = mounter;
this.frontendFactory = frontendFactory;
this.closer = closer;
this.cryptoFileSystemFactory = cryptoFileSystemFactory;
try {
setMountName(getName());
@@ -84,35 +82,34 @@ public class Vault implements Serializable {
return Files.isRegularFile(masterKeyPath);
}
public synchronized boolean startServer() {
namesOfResourcesWithInvalidMac.clear();
whitelistedResourcesWithInvalidMac.clear();
Optional<ServletLifeCycleAdapter> o = webDavServlet.get();
if (o.isPresent() && o.get().isRunning()) {
return false;
public void create(CharSequence passphrase) throws IOException {
try {
FileSystem fs = NioFileSystem.rootedAt(path);
if (fs.children().count() > 0) {
throw new FileAlreadyExistsException(null, null, "Vault location not empty.");
}
cryptoFileSystemFactory.get(fs, passphrase, this);
} catch (UncheckedIOException e) {
throw new IOException(e);
}
ServletLifeCycleAdapter servlet = server.createServlet(path, cryptor, namesOfResourcesWithInvalidMac, whitelistedResourcesWithInvalidMac, mountName);
if (servlet.start()) {
webDavServlet = closer.closeLater(servlet);
return true;
}
return false;
}
public void stopServer() {
public synchronized void activateFrontend(CharSequence passphrase) throws FrontendCreationFailedException {
try {
unmount();
} catch (CommandFailedException e) {
LOG.warn("Unmounting failed. Locking anyway...", e);
FileSystem fs = NioFileSystem.rootedAt(path);
FileSystem cryptoFs = cryptoFileSystemFactory.get(fs, passphrase, this);
String contextPath = StringUtils.prependIfMissing(mountName, "/");
Frontend frontend = frontendFactory.get().create(cryptoFs, contextPath);
filesystemFrontend = closer.closeLater(frontend);
setUnlocked(true);
} catch (UncheckedIOException e) {
throw new FrontendCreationFailedException(e);
}
webDavServlet.close();
try {
cryptor.destroy();
} catch (DestroyFailedException e) {
LOG.error("Destruction of cryptor throw an exception.", e);
}
whitelistedResourcesWithInvalidMac.clear();
namesOfResourcesWithInvalidMac.clear();
}
public synchronized void deactivateFrontend() {
filesystemFrontend.close();
setUnlocked(false);
}
private Map<MountParam, Optional<String>> getMountParams() {
@@ -123,32 +120,35 @@ public class Vault implements Serializable {
}
public Boolean mount() {
final ServletLifeCycleAdapter servlet = webDavServlet.get().orElse(null);
if (servlet == null || !servlet.isRunning()) {
return false;
}
try {
webDavMount = closer.closeLater(mounter.mount(servlet.getServletUri(), getMountParams()));
return true;
} catch (CommandFailedException e) {
LOG.warn("mount failed", e);
// TODO exception handling
Frontend frontend = filesystemFrontend.get().orElse(null);
if (frontend == null) {
return false;
} else {
return frontend.mount(getMountParams());
}
}
public void reveal() throws CommandFailedException {
final WebDavMount mnt = webDavMount.get().orElse(null);
if (mnt != null) {
mnt.reveal();
}
public void reveal() {
// TODO exception handling
filesystemFrontend.get().ifPresent(Frontend::reveal);
}
public void unmount() throws CommandFailedException {
final WebDavMount mnt = webDavMount.get().orElse(null);
if (mnt != null) {
mnt.unmount();
}
webDavMount = DeferredClosable.empty();
public void unmount() {
// TODO exception handling
filesystemFrontend.get().ifPresent(Frontend::unmount);
}
/* Delegate Methods */
@Override
public void authenticationFailed(String cleartextPath) {
namesOfResourcesWithInvalidMac.add(cleartextPath);
}
@Override
public boolean shouldSkipAuthentication(String cleartextPath) {
return namesOfResourcesWithInvalidMac.contains(cleartextPath);
}
/* Getter/Setter */
@@ -164,9 +164,9 @@ public class Vault implements Serializable {
return StringUtils.removeEnd(path.getFileName().toString(), VAULT_FILE_EXTENSION);
}
public Cryptor getCryptor() {
return cryptor;
}
// public Cryptor getCryptor() {
// return cryptor;
// }
public ObjectProperty<Boolean> unlockedProperty() {
return unlocked;

View File

@@ -3,33 +3,31 @@ package org.cryptomator.ui.model;
import java.nio.file.Path;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;
import org.cryptomator.crypto.Cryptor;
import org.cryptomator.filesystem.crypto.CryptoFileSystemFactory;
import org.cryptomator.frontend.FrontendFactory;
import org.cryptomator.frontend.webdav.mount.WebDavMounter;
import org.cryptomator.ui.util.DeferredCloser;
import org.cryptomator.ui.util.mount.WebDavMounter;
import org.cryptomator.webdav.WebDavServer;
import dagger.Lazy;
@Singleton
public class VaultFactory {
private final WebDavServer server;
private final Provider<Cryptor> cryptorProvider;
private final WebDavMounter mounter;
private final Lazy<FrontendFactory> frontendFactory;
private final CryptoFileSystemFactory cryptoFileSystemFactory;
private final DeferredCloser closer;
@Inject
public VaultFactory(WebDavServer server, @Named("SamplingCryptor") Provider<Cryptor> cryptorProvider, WebDavMounter mounter, DeferredCloser closer) {
this.server = server;
this.cryptorProvider = cryptorProvider;
this.mounter = mounter;
public VaultFactory(Lazy<FrontendFactory> frontendFactory, CryptoFileSystemFactory cryptoFileSystemFactory, WebDavMounter mounter, DeferredCloser closer) {
this.frontendFactory = frontendFactory;
this.cryptoFileSystemFactory = cryptoFileSystemFactory;
this.closer = closer;
}
public Vault createVault(Path path) {
return new Vault(path, server, cryptorProvider.get(), mounter, closer);
return new Vault(path, frontendFactory, cryptoFileSystemFactory, closer);
}
}