mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-18 02:31:27 +00:00
- Single Jetty instnace (fixes #19)
This commit is contained in:
@@ -8,6 +8,10 @@
|
||||
******************************************************************************/
|
||||
package org.cryptomator.webdav;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
@@ -18,6 +22,7 @@ import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.util.component.LifeCycle;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.eclipse.jetty.util.thread.ThreadPool;
|
||||
import org.slf4j.Logger;
|
||||
@@ -31,40 +36,33 @@ public final class WebDavServer {
|
||||
private static final int MAX_THREADS = 200;
|
||||
private static final int MIN_THREADS = 4;
|
||||
private static final int THREAD_IDLE_SECONDS = 20;
|
||||
private static final WebDavServer INSTANCE = new WebDavServer();
|
||||
private final Server server;
|
||||
private int port;
|
||||
private final ServerConnector localConnector;
|
||||
private final ServletContextHandler servletContext;
|
||||
|
||||
public WebDavServer() {
|
||||
public static WebDavServer getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private WebDavServer() {
|
||||
final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(MAX_PENDING_REQUESTS);
|
||||
final ThreadPool tp = new QueuedThreadPool(MAX_THREADS, MIN_THREADS, THREAD_IDLE_SECONDS, queue);
|
||||
server = new Server(tp);
|
||||
localConnector = new ServerConnector(server);
|
||||
localConnector.setHost(LOCALHOST);
|
||||
servletContext = new ServletContextHandler(ServletContextHandler.SESSIONS);
|
||||
servletContext.setContextPath("/");
|
||||
server.setConnectors(new Connector[] {localConnector});
|
||||
server.setHandler(servletContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param workDir Path of encrypted folder.
|
||||
* @param cryptor A fully initialized cryptor instance ready to en- or decrypt streams.
|
||||
* @return <code>true</code> upon success
|
||||
*/
|
||||
public synchronized boolean start(final String workDir, final boolean checkFileIntegrity, final Cryptor cryptor) {
|
||||
final ServerConnector connector = new ServerConnector(server);
|
||||
connector.setHost(LOCALHOST);
|
||||
|
||||
final String contextPath = "/";
|
||||
final String servletPathSpec = "/*";
|
||||
|
||||
final ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
|
||||
context.addServlet(getWebDavServletHolder(workDir, contextPath, checkFileIntegrity, cryptor), servletPathSpec);
|
||||
context.setContextPath(contextPath);
|
||||
server.setHandler(context);
|
||||
|
||||
public synchronized void start() {
|
||||
try {
|
||||
server.setConnectors(new Connector[] {connector});
|
||||
server.start();
|
||||
port = connector.getLocalPort();
|
||||
return true;
|
||||
LOG.info("Cryptomator is running on port {}", getPort());
|
||||
} catch (Exception ex) {
|
||||
LOG.error("Server couldn't be started", ex);
|
||||
return false;
|
||||
throw new RuntimeException("Server couldn't be started", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,14 +70,33 @@ public final class WebDavServer {
|
||||
return server.isRunning();
|
||||
}
|
||||
|
||||
public synchronized boolean stop() {
|
||||
public synchronized void stop() {
|
||||
try {
|
||||
server.stop();
|
||||
port = 0;
|
||||
} catch (Exception ex) {
|
||||
LOG.error("Server couldn't be stopped", ex);
|
||||
}
|
||||
return server.isStopped();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param workDir Path of encrypted folder.
|
||||
* @param cryptor A fully initialized cryptor instance ready to en- or decrypt streams.
|
||||
* @return servlet
|
||||
*/
|
||||
public ServletLifeCycleAdapter createServlet(final Path workDir, final boolean checkFileIntegrity, final Cryptor cryptor) {
|
||||
try {
|
||||
final URI uri = new URI(null, null, localConnector.getHost(), localConnector.getLocalPort(), "/" + UUID.randomUUID().toString(), null, null);
|
||||
|
||||
final String pathPrefix = uri.getRawPath() + "/";
|
||||
final String pathSpec = pathPrefix + "*";
|
||||
final ServletHolder servlet = getWebDavServletHolder(workDir.toString(), pathPrefix, checkFileIntegrity, cryptor);
|
||||
servletContext.addServlet(servlet, pathSpec);
|
||||
|
||||
LOG.info("{} available on http://{}", workDir, uri.getRawSchemeSpecificPart());
|
||||
return new ServletLifeCycleAdapter(servlet, uri);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalArgumentException("Can't create URI from given workDir", e);
|
||||
}
|
||||
}
|
||||
|
||||
private ServletHolder getWebDavServletHolder(final String workDir, final String contextPath, final boolean checkFileIntegrity, final Cryptor cryptor) {
|
||||
@@ -91,7 +108,50 @@ public final class WebDavServer {
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
return localConnector.getLocalPort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes implementation-specific methods to other modules.
|
||||
*/
|
||||
public class ServletLifeCycleAdapter {
|
||||
|
||||
private final LifeCycle lifecycle;
|
||||
private final URI servletUri;
|
||||
|
||||
private ServletLifeCycleAdapter(LifeCycle lifecycle, URI servletUri) {
|
||||
this.lifecycle = lifecycle;
|
||||
this.servletUri = servletUri;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return lifecycle.isRunning();
|
||||
}
|
||||
|
||||
public boolean start() {
|
||||
try {
|
||||
lifecycle.start();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to start", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean stop() {
|
||||
try {
|
||||
lifecycle.stop();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to stop", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public URI getServletUri() {
|
||||
return servletUri;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.DirectoryStream.Filter;
|
||||
import java.nio.file.Path;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
@@ -40,7 +41,6 @@ import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.security.auth.DestroyFailedException;
|
||||
import javax.security.auth.Destroyable;
|
||||
|
||||
import org.apache.commons.io.Charsets;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.io.output.NullOutputStream;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
@@ -330,7 +330,7 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
|
||||
final ByteBuffer iv = ByteBuffer.allocate(AES_BLOCK_LENGTH);
|
||||
iv.put(partialIv);
|
||||
final Cipher cipher = this.aesCtrCipher(key, iv.array(), Cipher.ENCRYPT_MODE);
|
||||
final byte[] cleartextBytes = cleartext.getBytes(Charsets.UTF_8);
|
||||
final byte[] cleartextBytes = cleartext.getBytes(StandardCharsets.UTF_8);
|
||||
final byte[] encryptedBytes = cipher.doFinal(cleartextBytes);
|
||||
final String ivAndCiphertext = ENCRYPTED_FILENAME_CODEC.encodeAsString(partialIv) + IV_PREFIX_SEPARATOR + ENCRYPTED_FILENAME_CODEC.encodeAsString(encryptedBytes);
|
||||
|
||||
@@ -387,7 +387,7 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
|
||||
final Cipher cipher = this.aesCtrCipher(key, iv.array(), Cipher.DECRYPT_MODE);
|
||||
final byte[] encryptedBytes = ENCRYPTED_FILENAME_CODEC.decode(ciphertext);
|
||||
final byte[] cleartextBytes = cipher.doFinal(encryptedBytes);
|
||||
return new String(cleartextBytes, Charsets.UTF_8);
|
||||
return new String(cleartextBytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private LongFilenameMetadata getMetadata(CryptorIOSupport ioSupport, String metadataFile) throws IOException {
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.ui.settings.Settings;
|
||||
import org.cryptomator.ui.util.ActiveWindowStyleSupport;
|
||||
import org.cryptomator.ui.util.TrayIconUtil;
|
||||
import org.cryptomator.webdav.WebDavServer;
|
||||
import org.eclipse.jetty.util.ConcurrentHashSet;
|
||||
|
||||
public class MainApplication extends Application {
|
||||
@@ -37,6 +38,7 @@ public class MainApplication extends Application {
|
||||
|
||||
@Override
|
||||
public void start(final Stage primaryStage) throws IOException {
|
||||
WebDavServer.getInstance().start();
|
||||
chooseNativeStylesheet();
|
||||
final ResourceBundle rb = ResourceBundle.getBundle("localization");
|
||||
final FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/main.fxml"), rb);
|
||||
@@ -67,6 +69,7 @@ public class MainApplication extends Application {
|
||||
|
||||
private void quit() {
|
||||
Platform.runLater(() -> {
|
||||
WebDavServer.getInstance().stop();
|
||||
CLEAN_SHUTDOWN_PERFORMER.run();
|
||||
Settings.save();
|
||||
Platform.exit();
|
||||
|
||||
@@ -27,6 +27,7 @@ import javafx.util.Duration;
|
||||
|
||||
import org.cryptomator.crypto.CryptorIOSampling;
|
||||
import org.cryptomator.ui.model.Directory;
|
||||
import org.cryptomator.webdav.WebDavServer;
|
||||
|
||||
public class UnlockedController implements Initializable {
|
||||
|
||||
@@ -123,7 +124,7 @@ public class UnlockedController implements Initializable {
|
||||
|
||||
public void setDirectory(Directory directory) {
|
||||
this.directory = directory;
|
||||
final String msg = String.format(rb.getString("unlocked.messageLabel.runningOnPort"), directory.getServer().getPort());
|
||||
final String msg = String.format(rb.getString("unlocked.messageLabel.runningOnPort"), WebDavServer.getInstance().getPort());
|
||||
messageLabel.setText(msg);
|
||||
|
||||
if (directory.getCryptor() instanceof CryptorIOSampling) {
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.cryptomator.ui.util.mount.CommandFailedException;
|
||||
import org.cryptomator.ui.util.mount.WebDavMount;
|
||||
import org.cryptomator.ui.util.mount.WebDavMounter;
|
||||
import org.cryptomator.webdav.WebDavServer;
|
||||
import org.cryptomator.webdav.WebDavServer.ServletLifeCycleAdapter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -29,20 +30,20 @@ public class Directory implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 3754487289683599469L;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Directory.class);
|
||||
|
||||
private final WebDavServer server = new WebDavServer();
|
||||
private final Cryptor cryptor = SamplingDecorator.decorate(new Aes256Cryptor());
|
||||
private final ObjectProperty<Boolean> unlocked = new SimpleObjectProperty<Boolean>(this, "unlocked", Boolean.FALSE);
|
||||
private final Runnable shutdownTask = new ShutdownTask();
|
||||
private final Path path;
|
||||
private boolean verifyFileIntegrity;
|
||||
private ServletLifeCycleAdapter webDavServlet;
|
||||
private WebDavMount webDavMount;
|
||||
private final Runnable shutdownTask = new ShutdownTask();
|
||||
|
||||
public Directory(final Path path) {
|
||||
if (!Files.isDirectory(path)) {
|
||||
throw new IllegalArgumentException("Not a directory: " + path);
|
||||
}
|
||||
this.path = path;
|
||||
|
||||
}
|
||||
|
||||
public boolean containsMasterKey() throws IOException {
|
||||
@@ -50,7 +51,11 @@ public class Directory implements Serializable {
|
||||
}
|
||||
|
||||
public synchronized boolean startServer() {
|
||||
if (server.start(path.toString(), verifyFileIntegrity, cryptor)) {
|
||||
if (webDavServlet != null && webDavServlet.isRunning()) {
|
||||
return false;
|
||||
}
|
||||
webDavServlet = WebDavServer.getInstance().createServlet(path, verifyFileIntegrity, cryptor);
|
||||
if (webDavServlet.start()) {
|
||||
MainApplication.addShutdownTask(shutdownTask);
|
||||
return true;
|
||||
} else {
|
||||
@@ -58,18 +63,21 @@ public class Directory implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void stopServer() {
|
||||
if (server.isRunning()) {
|
||||
public void stopServer() {
|
||||
if (webDavServlet != null && webDavServlet.isRunning()) {
|
||||
MainApplication.removeShutdownTask(shutdownTask);
|
||||
this.unmount();
|
||||
server.stop();
|
||||
webDavServlet.stop();
|
||||
cryptor.swipeSensitiveData();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean mount() {
|
||||
if (webDavServlet == null || !webDavServlet.isRunning()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
webDavMount = WebDavMounter.mount(server.getPort());
|
||||
webDavMount = WebDavMounter.mount(webDavServlet.getServletUri());
|
||||
return true;
|
||||
} catch (CommandFailedException e) {
|
||||
LOG.warn("mount failed", e);
|
||||
@@ -127,10 +135,6 @@ public class Directory implements Serializable {
|
||||
this.unlocked.set(unlocked);
|
||||
}
|
||||
|
||||
public WebDavServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
/* hashcode/equals */
|
||||
|
||||
@Override
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui.util.mount;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* A WebDavMounter acting as fallback if no other mounter works.
|
||||
*
|
||||
@@ -21,7 +23,7 @@ final class FallbackWebDavMounter implements WebDavMounterStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebDavMount mount(int localPort) {
|
||||
public WebDavMount mount(URI uri) {
|
||||
displayMountInstructions();
|
||||
return new WebDavMount() {
|
||||
@Override
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui.util.mount;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.ui.util.command.Script;
|
||||
|
||||
@@ -30,16 +32,16 @@ final class LinuxGvfsWebDavMounter implements WebDavMounterStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebDavMount mount(int localPort) throws CommandFailedException {
|
||||
public WebDavMount mount(URI uri) throws CommandFailedException {
|
||||
final Script mountScript = Script.fromLines(
|
||||
"set -x",
|
||||
"gvfs-mount \"dav://[::1]:$PORT\"",
|
||||
"xdg-open \"$URI\"")
|
||||
.addEnv("PORT", String.valueOf(localPort));
|
||||
"gvfs-mount \"dav:$DAV_SSP\"",
|
||||
"xdg-open \"dav:$DAV_SSP\"")
|
||||
.addEnv("DAV_SSP", uri.getRawSchemeSpecificPart());
|
||||
final Script unmountScript = Script.fromLines(
|
||||
"set -x",
|
||||
"gvfs-mount -u \"dav://[::1]:$PORT\"")
|
||||
.addEnv("URI", String.valueOf(localPort));
|
||||
"gvfs-mount -u \"dav:$DAV_SSP\"")
|
||||
.addEnv("$DAV_SSP", uri.getRawSchemeSpecificPart());
|
||||
mountScript.execute();
|
||||
return new WebDavMount() {
|
||||
@Override
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui.util.mount;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.ui.util.command.Script;
|
||||
|
||||
@@ -20,13 +22,14 @@ final class MacOsXWebDavMounter implements WebDavMounterStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebDavMount mount(int localPort) throws CommandFailedException {
|
||||
final String path = "/Volumes/Cryptomator" + localPort;
|
||||
public WebDavMount mount(URI uri) throws CommandFailedException {
|
||||
final String path = "/Volumes/Cryptomator" + uri.getRawPath().replace('/', '_');
|
||||
final Script mountScript = Script.fromLines(
|
||||
"mkdir \"$MOUNT_PATH\"",
|
||||
"mount_webdav -S -v Cryptomator \"[::1]:$PORT\" \"$MOUNT_PATH\"",
|
||||
"mount_webdav -S -v Cryptomator \"[::1]:$PORT$DAV_PATH\" \"$MOUNT_PATH\"",
|
||||
"open \"$MOUNT_PATH\"")
|
||||
.addEnv("PORT", String.valueOf(localPort))
|
||||
.addEnv("PORT", String.valueOf(uri.getPort()))
|
||||
.addEnv("DAV_PATH", uri.getRawPath())
|
||||
.addEnv("MOUNT_PATH", path);
|
||||
final Script unmountScript = Script.fromLines(
|
||||
"umount $MOUNT_PATH")
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui.util.mount;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -23,12 +25,12 @@ public final class WebDavMounter {
|
||||
/**
|
||||
* Tries to mount a given webdav share.
|
||||
*
|
||||
* @param localPort local TCP port of the webdav share
|
||||
* @param uri URI of the webdav share
|
||||
* @return a {@link WebDavMount} representing the mounted share
|
||||
* @throws CommandFailedException if the mount operation fails
|
||||
*/
|
||||
public static WebDavMount mount(int localPort) throws CommandFailedException {
|
||||
return chooseStrategy().mount(localPort);
|
||||
public static WebDavMount mount(URI uri) throws CommandFailedException {
|
||||
return chooseStrategy().mount(uri);
|
||||
}
|
||||
|
||||
private static WebDavMounterStrategy chooseStrategy() {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui.util.mount;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* A strategy able to mount a webdav share and display it to the user.
|
||||
@@ -25,10 +26,10 @@ interface WebDavMounterStrategy {
|
||||
/**
|
||||
* Tries to mount a given webdav share.
|
||||
*
|
||||
* @param localPort local TCP port of the webdav share
|
||||
* @param uri URI of the webdav share
|
||||
* @return a {@link WebDavMount} representing the mounted share
|
||||
* @throws CommandFailedException if the mount operation fails
|
||||
*/
|
||||
WebDavMount mount(int localPort) throws CommandFailedException;
|
||||
WebDavMount mount(URI uri) throws CommandFailedException;
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ package org.cryptomator.ui.util.mount;
|
||||
|
||||
import static org.cryptomator.ui.util.command.Script.fromLines;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -36,8 +37,10 @@ final class WindowsWebDavMounter implements WebDavMounterStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebDavMount mount(int localPort) throws CommandFailedException {
|
||||
final Script mountScript = fromLines("net use * http://0--1.ipv6-literal.net:%PORT% /persistent:no").addEnv("PORT", String.valueOf(localPort));
|
||||
public WebDavMount mount(URI uri) throws CommandFailedException {
|
||||
final Script mountScript = fromLines("net use * http://0--1.ipv6-literal.net:%PORT%%DAV_PATH% /persistent:no")
|
||||
.addEnv("PORT", String.valueOf(uri.getPort()))
|
||||
.addEnv("DAV_PATH", uri.getRawPath());
|
||||
final CommandResult mountResult = mountScript.execute(30, TimeUnit.SECONDS);
|
||||
final String driveLetter = getDriveLetter(mountResult.getStdOut());
|
||||
final Script unmountScript = fromLines("net use " + driveLetter + " /delete").addEnv("DRIVE_LETTER", driveLetter);
|
||||
|
||||
Reference in New Issue
Block a user