mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-25 06:01:31 +00:00
fixes #74
This commit is contained in:
committed by
Sebastian Stenzel
parent
ca88e05849
commit
b691e374eb
@@ -77,7 +77,7 @@ class CryptomatorModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
WebDavMounter provideWebDavMounterProvider(WebDavMounterProvider webDavMounterProvider) {
|
||||
WebDavMounter provideWebDavMounter(WebDavMounterProvider webDavMounterProvider) {
|
||||
return webDavMounterProvider.get();
|
||||
}
|
||||
|
||||
|
||||
@@ -15,14 +15,32 @@ 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 javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ChoiceBox;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.ProgressIndicator;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
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;
|
||||
@@ -30,22 +48,10 @@ 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;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.ProgressIndicator;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.text.Text;
|
||||
|
||||
public class UnlockController extends AbstractFXMLViewController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UnlockController.class);
|
||||
@@ -58,6 +64,9 @@ public class UnlockController extends AbstractFXMLViewController {
|
||||
|
||||
@FXML
|
||||
private TextField mountName;
|
||||
|
||||
@FXML
|
||||
private ChoiceBox<Character> winDriveLetter;
|
||||
|
||||
@FXML
|
||||
private Button advancedOptionsButton;
|
||||
@@ -79,11 +88,14 @@ public class UnlockController extends AbstractFXMLViewController {
|
||||
|
||||
private final ExecutorService exec;
|
||||
private final Application app;
|
||||
private final WindowsDriveLetters driveLetters;
|
||||
private final ChangeListener<Character> driveLetterChangeListener = this::winDriveLetterDidChange;
|
||||
|
||||
@Inject
|
||||
public UnlockController(Application app, ExecutorService exec) {
|
||||
public UnlockController(Application app, ExecutorService exec, WindowsDriveLetters driveLetters) {
|
||||
this.app = app;
|
||||
this.exec = exec;
|
||||
this.driveLetters = driveLetters;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -99,17 +111,31 @@ public class UnlockController extends AbstractFXMLViewController {
|
||||
@Override
|
||||
public void initialize() {
|
||||
passwordField.textProperty().addListener(this::passwordFieldsDidChange);
|
||||
advancedOptions.managedProperty().bind(advancedOptions.visibleProperty());
|
||||
mountName.addEventFilter(KeyEvent.KEY_TYPED, this::filterAlphanumericKeyEvents);
|
||||
mountName.textProperty().addListener(this::mountNameDidChange);
|
||||
advancedOptions.managedProperty().bind(advancedOptions.visibleProperty());
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
winDriveLetter.setConverter(new WinDriveLetterLabelConverter());
|
||||
} else {
|
||||
winDriveLetter.setVisible(false);
|
||||
winDriveLetter.setManaged(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void resetView() {
|
||||
passwordField.clear();
|
||||
unlockButton.setDisable(true);
|
||||
advancedOptions.setVisible(false);
|
||||
advancedOptionsButton.setText(resourceBundle.getString("unlock.button.advancedOptions.show"));
|
||||
progressIndicator.setVisible(false);
|
||||
passwordField.clear();
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
winDriveLetter.valueProperty().removeListener(driveLetterChangeListener);
|
||||
winDriveLetter.getItems().clear();
|
||||
winDriveLetter.getItems().add(null);
|
||||
winDriveLetter.getItems().addAll(driveLetters.getAvailableDriveLetters());
|
||||
winDriveLetter.getItems().sort(new WinDriveLetterComparator());
|
||||
winDriveLetter.valueProperty().addListener(driveLetterChangeListener);
|
||||
}
|
||||
downloadsPageLink.setVisible(false);
|
||||
messageText.setText(null);
|
||||
}
|
||||
@@ -145,6 +171,77 @@ public class UnlockController extends AbstractFXMLViewController {
|
||||
advancedOptionsButton.setText(resourceBundle.getString("unlock.button.advancedOptions.show"));
|
||||
}
|
||||
}
|
||||
|
||||
private void filterAlphanumericKeyEvents(KeyEvent t) {
|
||||
if (t.getCharacter() == null || t.getCharacter().length() == 0) {
|
||||
return;
|
||||
}
|
||||
char c = CharUtils.toChar(t.getCharacter());
|
||||
if (!(CharUtils.isAsciiAlphanumeric(c) || c == '_')) {
|
||||
t.consume();
|
||||
}
|
||||
}
|
||||
|
||||
private void mountNameDidChange(ObservableValue<? extends String> property, String oldValue, String newValue) {
|
||||
if (vault == null) {
|
||||
return;
|
||||
}
|
||||
// newValue is guaranteed to be a-z0-9_, see #filterAlphanumericKeyEvents
|
||||
if (newValue.isEmpty()) {
|
||||
mountName.setText(vault.getMountName());
|
||||
} else {
|
||||
vault.setMountName(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts 'C' to "C:" to translate between model and GUI.
|
||||
*/
|
||||
private class WinDriveLetterLabelConverter extends StringConverter<Character> {
|
||||
|
||||
@Override
|
||||
public String toString(Character letter) {
|
||||
if (letter == null) {
|
||||
return resourceBundle.getString("unlock.choicebox.winDriveLetter.auto");
|
||||
} else {
|
||||
return Character.toString(letter) + ":";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Character fromString(String string) {
|
||||
if (resourceBundle.getString("unlock.choicebox.winDriveLetter.auto").equals(string)) {
|
||||
return null;
|
||||
} else {
|
||||
return CharUtils.toCharacterObject(string);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Natural sorting of ASCII letters, but <code>null</code> always on first, as this is "auto-assign".
|
||||
*/
|
||||
private static class WinDriveLetterComparator implements Comparator<Character> {
|
||||
|
||||
@Override
|
||||
public int compare(Character c1, Character c2) {
|
||||
if (c1 == null) {
|
||||
return -1;
|
||||
} else if (c2 == null) {
|
||||
return 1;
|
||||
} else {
|
||||
return (char) c1 - (char) c2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void winDriveLetterDidChange(ObservableValue<? extends Character> property, Character oldValue, Character newValue) {
|
||||
if (vault == null) {
|
||||
return;
|
||||
}
|
||||
vault.setWinDriveLetter(newValue);
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// Unlock button
|
||||
@@ -168,7 +265,7 @@ public class UnlockController extends AbstractFXMLViewController {
|
||||
// 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(() -> (boolean) vault.mount());
|
||||
final Future<Boolean> futureMount = exec.submit(vault::mount);
|
||||
FXThreads.runOnMainThreadWhenFinished(exec, futureMount, this::unlockAndMountFinished);
|
||||
} catch (IOException ex) {
|
||||
setControlsDisabled(false);
|
||||
@@ -228,25 +325,6 @@ public class UnlockController extends AbstractFXMLViewController {
|
||||
}
|
||||
}
|
||||
|
||||
public void filterAlphanumericKeyEvents(KeyEvent t) {
|
||||
if (t.getCharacter() == null || t.getCharacter().length() == 0) {
|
||||
return;
|
||||
}
|
||||
char c = t.getCharacter().charAt(0);
|
||||
if (!CharUtils.isAsciiAlphanumeric(c)) {
|
||||
t.consume();
|
||||
}
|
||||
}
|
||||
|
||||
private void mountNameDidChange(ObservableValue<? extends String> property, String oldValue, String newValue) {
|
||||
// newValue is guaranteed to be a-z0-9, see #filterAlphanumericKeyEvents
|
||||
if (newValue.isEmpty()) {
|
||||
mountName.setText(vault.getMountName());
|
||||
} else {
|
||||
vault.setMountName(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public Vault getVault() {
|
||||
@@ -257,6 +335,24 @@ public class UnlockController extends AbstractFXMLViewController {
|
||||
this.resetView();
|
||||
this.vault = vault;
|
||||
this.mountName.setText(vault.getMountName());
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
chooseSelectedDriveLetter();
|
||||
}
|
||||
}
|
||||
|
||||
private void chooseSelectedDriveLetter() {
|
||||
assert SystemUtils.IS_OS_WINDOWS;
|
||||
// if the vault prefers a drive letter, that is currently occupied, this is our last chance to reset this:
|
||||
if (driveLetters.getOccupiedDriveLetters().contains(vault.getWinDriveLetter())) {
|
||||
vault.setWinDriveLetter(null);
|
||||
}
|
||||
final Character letter = vault.getWinDriveLetter();
|
||||
if (letter == null) {
|
||||
// first option is known to be 'auto-assign' due to #WinDriveLetterComparator.
|
||||
this.winDriveLetter.getSelectionModel().selectFirst();
|
||||
} else {
|
||||
this.winDriveLetter.getSelectionModel().select(letter);
|
||||
}
|
||||
}
|
||||
|
||||
public UnlockListener getListener() {
|
||||
|
||||
@@ -7,11 +7,18 @@ import java.nio.file.Path;
|
||||
import java.text.Normalizer;
|
||||
import java.text.Normalizer.Form;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
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.ui.util.DeferredClosable;
|
||||
@@ -20,15 +27,13 @@ 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 javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
public class Vault implements Serializable {
|
||||
|
||||
@@ -49,6 +54,7 @@ public class Vault implements Serializable {
|
||||
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();
|
||||
|
||||
@@ -108,14 +114,21 @@ public class Vault implements Serializable {
|
||||
whitelistedResourcesWithInvalidMac.clear();
|
||||
namesOfResourcesWithInvalidMac.clear();
|
||||
}
|
||||
|
||||
private Map<MountParam, Optional<String>> getMountParams() {
|
||||
return ImmutableMap.of( //
|
||||
MountParam.MOUNT_NAME, Optional.ofNullable(mountName), //
|
||||
MountParam.WIN_DRIVE_LETTER, Optional.ofNullable(CharUtils.toString(winDriveLetter)) //
|
||||
);
|
||||
}
|
||||
|
||||
public boolean mount() {
|
||||
Optional<ServletLifeCycleAdapter> o = webDavServlet.get();
|
||||
if (!o.isPresent() || !o.get().isRunning()) {
|
||||
final ServletLifeCycleAdapter servlet = webDavServlet.get().orElse(null);
|
||||
if (servlet == null || !servlet.isRunning()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
webDavMount = closer.closeLater(mounter.mount(o.get().getServletUri(), mountName));
|
||||
webDavMount = closer.closeLater(mounter.mount(servlet.getServletUri(), getMountParams()));
|
||||
return true;
|
||||
} catch (CommandFailedException e) {
|
||||
LOG.warn("mount failed", e);
|
||||
@@ -167,9 +180,7 @@ public class Vault implements Serializable {
|
||||
this.unlocked.set(unlocked);
|
||||
}
|
||||
|
||||
public String getMountName() {
|
||||
return mountName;
|
||||
}
|
||||
|
||||
|
||||
public ObservableList<String> getNamesOfResourcesWithInvalidMac() {
|
||||
return namesOfResourcesWithInvalidMac;
|
||||
@@ -204,6 +215,10 @@ public class Vault implements Serializable {
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public String getMountName() {
|
||||
return mountName;
|
||||
}
|
||||
|
||||
/**
|
||||
* sets the mount name while normalizing it
|
||||
@@ -218,6 +233,14 @@ public class Vault implements Serializable {
|
||||
}
|
||||
this.mountName = mountName;
|
||||
}
|
||||
|
||||
public Character getWinDriveLetter() {
|
||||
return winDriveLetter;
|
||||
}
|
||||
|
||||
public void setWinDriveLetter(Character winDriveLetter) {
|
||||
this.winDriveLetter = winDriveLetter;
|
||||
}
|
||||
|
||||
/* hashcode/equals */
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.lang3.CharUtils;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
@@ -45,7 +47,11 @@ public class VaultObjectMapperProvider implements Provider<ObjectMapper> {
|
||||
public void serialize(Vault value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
|
||||
jgen.writeStartObject();
|
||||
jgen.writeStringField("path", value.getPath().toString());
|
||||
jgen.writeStringField("mountName", value.getMountName().toString());
|
||||
jgen.writeStringField("mountName", value.getMountName());
|
||||
final Character winDriveLetter = value.getWinDriveLetter();
|
||||
if (winDriveLetter != null) {
|
||||
jgen.writeStringField("winDriveLetter", Character.toString(winDriveLetter));
|
||||
}
|
||||
jgen.writeEndObject();
|
||||
}
|
||||
|
||||
@@ -62,6 +68,9 @@ public class VaultObjectMapperProvider implements Provider<ObjectMapper> {
|
||||
if (node.has("mountName")) {
|
||||
vault.setMountName(node.get("mountName").asText());
|
||||
}
|
||||
if (node.has("winDriveLetter")) {
|
||||
vault.setWinDriveLetter(CharUtils.toCharacterObject(node.get("winDriveLetter").asText()));
|
||||
}
|
||||
return vault;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
package org.cryptomator.ui.util.mount;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A WebDavMounter acting as fallback if no other mounter works.
|
||||
@@ -28,7 +30,7 @@ final class FallbackWebDavMounter implements WebDavMounterStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebDavMount mount(URI uri, String name) {
|
||||
public WebDavMount mount(URI uri, Map<MountParam, Optional<String>> mountParams) {
|
||||
displayMountInstructions();
|
||||
return new AbstractWebDavMount() {
|
||||
@Override
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
package org.cryptomator.ui.util.mount;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -22,9 +24,7 @@ import org.cryptomator.ui.util.command.Script;
|
||||
final class LinuxGvfsWebDavMounter implements WebDavMounterStrategy {
|
||||
|
||||
@Inject
|
||||
LinuxGvfsWebDavMounter() {
|
||||
|
||||
}
|
||||
LinuxGvfsWebDavMounter() {}
|
||||
|
||||
@Override
|
||||
public boolean shouldWork() {
|
||||
@@ -47,52 +47,54 @@ final class LinuxGvfsWebDavMounter implements WebDavMounterStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebDavMount mount(URI uri, String name) throws CommandFailedException {
|
||||
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 testMountStillExistsScript = Script.fromLines(
|
||||
"set -x",
|
||||
"test `gvfs-mount --list | grep \"$DAV_SSP\" | wc -l` -eq 1")
|
||||
.addEnv("DAV_SSP", uri.getRawSchemeSpecificPart());
|
||||
final Script unmountScript = Script.fromLines(
|
||||
"set -x",
|
||||
"gvfs-mount -u \"dav:$DAV_SSP\"")
|
||||
.addEnv("DAV_SSP", uri.getRawSchemeSpecificPart());
|
||||
mountScript.execute();
|
||||
return new AbstractWebDavMount() {
|
||||
@Override
|
||||
public void unmount() throws CommandFailedException {
|
||||
boolean mountStillExists;
|
||||
try {
|
||||
testMountStillExistsScript.execute();
|
||||
mountStillExists = true;
|
||||
} catch(CommandFailedException e) {
|
||||
mountStillExists = false;
|
||||
}
|
||||
// only attempt unmount if user didn't unmount manually:
|
||||
if (mountStillExists) {
|
||||
unmountScript.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reveal() throws CommandFailedException {
|
||||
try {
|
||||
openMountWithWebdavUri("dav:"+uri.getRawSchemeSpecificPart()).execute();
|
||||
} catch (CommandFailedException exception) {
|
||||
openMountWithWebdavUri("webdav:"+uri.getRawSchemeSpecificPart()).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) {
|
||||
mountStillExists = false;
|
||||
}
|
||||
// only attempt unmount if user didn't unmount manually:
|
||||
if (mountStillExists) {
|
||||
unmountScript.execute();
|
||||
}
|
||||
}
|
||||
|
||||
private Script openMountWithWebdavUri(String webdavUri){
|
||||
return Script.fromLines(
|
||||
"set -x",
|
||||
"xdg-open \"$DAV_URI\"")
|
||||
.addEnv("DAV_URI", webdavUri);
|
||||
@Override
|
||||
public void reveal() throws CommandFailedException {
|
||||
try {
|
||||
openMountWithWebdavUri("dav:"+webDavUri.getRawSchemeSpecificPart()).execute();
|
||||
} catch (CommandFailedException exception) {
|
||||
openMountWithWebdavUri("webdav:"+webDavUri.getRawSchemeSpecificPart()).execute();
|
||||
}
|
||||
}
|
||||
|
||||
private Script openMountWithWebdavUri(String webdavUri){
|
||||
return Script.fromLines("set -x", "xdg-open \"$DAV_URI\"").addEnv("DAV_URI", webdavUri);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ package org.cryptomator.ui.util.mount;
|
||||
import java.net.URI;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -24,9 +26,7 @@ import org.cryptomator.ui.util.command.Script;
|
||||
final class MacOsXWebDavMounter implements WebDavMounterStrategy {
|
||||
|
||||
@Inject
|
||||
MacOsXWebDavMounter() {
|
||||
|
||||
}
|
||||
MacOsXWebDavMounter() {}
|
||||
|
||||
@Override
|
||||
public boolean shouldWork() {
|
||||
@@ -39,7 +39,11 @@ final class MacOsXWebDavMounter implements WebDavMounterStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebDavMount mount(URI uri, String name) throws CommandFailedException {
|
||||
public WebDavMount mount(URI uri, Map<MountParam, Optional<String>> mountParams) throws CommandFailedException {
|
||||
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(
|
||||
@@ -48,28 +52,35 @@ final class MacOsXWebDavMounter implements WebDavMounterStrategy {
|
||||
.addEnv("DAV_AUTHORITY", uri.getRawAuthority())
|
||||
.addEnv("DAV_PATH", uri.getRawPath())
|
||||
.addEnv("MOUNT_PATH", path)
|
||||
.addEnv("MOUNT_NAME", name);
|
||||
final Script revealScript = Script.fromLines(
|
||||
"open \"$MOUNT_PATH\"")
|
||||
.addEnv("MOUNT_PATH", path);
|
||||
final Script unmountScript = Script.fromLines(
|
||||
"diskutil umount $MOUNT_PATH")
|
||||
.addEnv("MOUNT_PATH", path);
|
||||
.addEnv("MOUNT_NAME", mountName);
|
||||
mountScript.execute();
|
||||
return new AbstractWebDavMount() {
|
||||
@Override
|
||||
public void unmount() throws CommandFailedException {
|
||||
// only attempt unmount if user didn't unmount manually:
|
||||
if (Files.exists(FileSystems.getDefault().getPath(path))) {
|
||||
unmountScript.execute();
|
||||
}
|
||||
return new MacWebDavMount(path);
|
||||
}
|
||||
|
||||
private static class MacWebDavMount extends AbstractWebDavMount {
|
||||
private final String mountPath;
|
||||
private final Script revealScript;
|
||||
private final Script unmountScript;
|
||||
|
||||
private MacWebDavMount(String mountPath) {
|
||||
this.mountPath = mountPath;
|
||||
this.revealScript = Script.fromLines("open \"$MOUNT_PATH\"").addEnv("MOUNT_PATH", mountPath);
|
||||
this.unmountScript = Script.fromLines("diskutil umount $MOUNT_PATH").addEnv("MOUNT_PATH", mountPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unmount() throws CommandFailedException {
|
||||
// only attempt unmount if user didn't unmount manually:
|
||||
if (Files.exists(FileSystems.getDefault().getPath(mountPath))) {
|
||||
unmountScript.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reveal() throws CommandFailedException {
|
||||
revealScript.execute();
|
||||
}
|
||||
};
|
||||
@Override
|
||||
public void reveal() throws CommandFailedException {
|
||||
revealScript.execute();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,17 +10,22 @@
|
||||
package org.cryptomator.ui.util.mount;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface WebDavMounter {
|
||||
|
||||
public static enum MountParam {MOUNT_NAME, WIN_DRIVE_LETTER}
|
||||
|
||||
/**
|
||||
* Tries to mount a given webdav share.
|
||||
*
|
||||
* @param uri URI of the webdav share
|
||||
* @param name the name under which the folder is to be mounted. This might be ignored.
|
||||
* @param mountParams additional mount parameters, that might not get ignored by some OS-specific mounters.
|
||||
* @return a {@link WebDavMount} representing the mounted share
|
||||
* @throws CommandFailedException if the mount operation fails
|
||||
* @throws IllegalArgumentException if mountParams is missing expected options
|
||||
*/
|
||||
WebDavMount mount(URI uri, String name) throws CommandFailedException;
|
||||
WebDavMount mount(URI uri, Map<MountParam, Optional<String>> mountParams) throws CommandFailedException;
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ import java.util.stream.StreamSupport;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.lang3.CharUtils;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
|
||||
@@ -24,8 +27,11 @@ public final class WindowsDriveLetters {
|
||||
}
|
||||
|
||||
public Set<Character> getOccupiedDriveLetters() {
|
||||
if (!SystemUtils.IS_OS_WINDOWS) {
|
||||
throw new UnsupportedOperationException("This method is only defined for Windows file systems");
|
||||
}
|
||||
Iterable<Path> rootDirs = FileSystems.getDefault().getRootDirectories();
|
||||
return StreamSupport.stream(rootDirs.spliterator(), false).map(path -> path.toString().toUpperCase().charAt(0)).collect(toSet());
|
||||
return StreamSupport.stream(rootDirs.spliterator(), false).map(Path::toString).map(CharUtils::toChar).map(Character::toUpperCase).collect(toSet());
|
||||
}
|
||||
|
||||
public Set<Character> getAvailableDriveLetters() {
|
||||
|
||||
@@ -12,8 +12,8 @@ package org.cryptomator.ui.util.mount;
|
||||
import static org.cryptomator.ui.util.command.Script.fromLines;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -21,6 +21,7 @@ import java.util.regex.Pattern;
|
||||
import javax.inject.Inject;
|
||||
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;
|
||||
@@ -31,10 +32,11 @@ import org.cryptomator.ui.util.command.Script;
|
||||
* Tested on Windows 7 but should also work on Windows 8.
|
||||
*/
|
||||
@Singleton
|
||||
public final class WindowsWebDavMounter implements WebDavMounterStrategy {
|
||||
final class WindowsWebDavMounter implements WebDavMounterStrategy {
|
||||
|
||||
private static final Pattern WIN_MOUNT_DRIVELETTER_PATTERN = Pattern.compile("\\s*([A-Z]:)\\s*");
|
||||
private static final Pattern WIN_MOUNT_DRIVELETTER_PATTERN = Pattern.compile("\\s*([A-Z]):\\s*");
|
||||
private static final int MAX_MOUNT_ATTEMPTS = 8;
|
||||
private static final char AUTO_ASSIGN_DRIVE_LETTER = '*';
|
||||
private final WindowsDriveLetters driveLetters;
|
||||
|
||||
@Inject
|
||||
@@ -53,50 +55,32 @@ public final class WindowsWebDavMounter implements WebDavMounterStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebDavMount mount(URI uri, String name) throws CommandFailedException {
|
||||
public WebDavMount mount(URI uri, Map<MountParam, Optional<String>> mountParams) throws CommandFailedException {
|
||||
final Character driveLetter = mountParams.get(MountParam.WIN_DRIVE_LETTER).map(CharUtils::toCharacterObject).orElse(AUTO_ASSIGN_DRIVE_LETTER);
|
||||
if (driveLetters.getOccupiedDriveLetters().contains(driveLetter)) {
|
||||
throw new CommandFailedException("Drive letter occupied.");
|
||||
}
|
||||
|
||||
final String driveLetterStr = driveLetter.charValue() == AUTO_ASSIGN_DRIVE_LETTER ? CharUtils.toString(AUTO_ASSIGN_DRIVE_LETTER) : driveLetter + ":";
|
||||
final Script localhostMountScript = fromLines("net use %DRIVE_LETTER% \\\\localhost@%DAV_PORT%\\DavWWWRoot%DAV_UNC_PATH% /persistent:no");
|
||||
localhostMountScript.addEnv("DRIVE_LETTER", driveLetterStr);
|
||||
localhostMountScript.addEnv("DAV_PORT", String.valueOf(uri.getPort()));
|
||||
localhostMountScript.addEnv("DAV_UNC_PATH", uri.getRawPath().replace('/', '\\'));
|
||||
CommandResult mountResult;
|
||||
try {
|
||||
final Script mountScript = fromLines("net use * \\\\localhost@%DAV_PORT%\\DavWWWRoot%DAV_UNC_PATH% /persistent:no");
|
||||
mountScript.addEnv("DAV_PORT", String.valueOf(uri.getPort())).addEnv("DAV_UNC_PATH", uri.getRawPath().replace('/', '\\'));
|
||||
mountResult = mountScript.execute(5, TimeUnit.SECONDS);
|
||||
mountResult = localhostMountScript.execute(5, TimeUnit.SECONDS);
|
||||
} catch (CommandFailedException ex) {
|
||||
final Script localhostMountScript = fromLines("net use * \\\\localhost@%DAV_PORT%\\DavWWWRoot%DAV_UNC_PATH% /persistent:no");
|
||||
localhostMountScript.addEnv("DAV_PORT", String.valueOf(uri.getPort())).addEnv("DAV_UNC_PATH", uri.getRawPath().replace('/', '\\'));
|
||||
final Script ipv6literaltMountScript = fromLines("net use * \\\\0--1.ipv6-literal.net@%DAV_PORT%\\DavWWWRoot%DAV_UNC_PATH% /persistent:no");
|
||||
ipv6literaltMountScript.addEnv("DAV_PORT", String.valueOf(uri.getPort())).addEnv("DAV_UNC_PATH", uri.getRawPath().replace('/', '\\'));
|
||||
final Script ipv6literaltMountScript = fromLines("net use %DRIVE_LETTER% \\\\0--1.ipv6-literal.net@%DAV_PORT%\\DavWWWRoot%DAV_UNC_PATH% /persistent:no");
|
||||
ipv6literaltMountScript.addEnv("DRIVE_LETTER", driveLetterStr);
|
||||
ipv6literaltMountScript.addEnv("DAV_PORT", String.valueOf(uri.getPort()));
|
||||
ipv6literaltMountScript.addEnv("DAV_UNC_PATH", uri.getRawPath().replace('/', '\\'));
|
||||
final Script proxyBypassScript = fromLines("reg add \"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\" /v \"ProxyOverride\" /d \"<local>;0--1.ipv6-literal.net;0--1.ipv6-literal.net:%DAV_PORT%\" /f");
|
||||
proxyBypassScript.addEnv("DAV_PORT", String.valueOf(uri.getPort()));
|
||||
mountResult = bypassProxyAndRetryMount(localhostMountScript, ipv6literaltMountScript, proxyBypassScript);
|
||||
}
|
||||
|
||||
final String driveLetter = getDriveLetter(mountResult.getStdOut());
|
||||
final Script openExplorerScript = fromLines("start explorer.exe " + driveLetter);
|
||||
final Script unmountScript = fromLines("net use " + driveLetter + " /delete").addEnv("DRIVE_LETTER", driveLetter);
|
||||
return new AbstractWebDavMount() {
|
||||
@Override
|
||||
public void unmount() throws CommandFailedException {
|
||||
// only attempt unmount if user didn't unmount manually:
|
||||
if (isVolumeMounted(driveLetter)) {
|
||||
unmountScript.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reveal() throws CommandFailedException {
|
||||
openExplorerScript.execute();
|
||||
}
|
||||
};
|
||||
return new WindowsWebDavMount(driveLetter.charValue() == AUTO_ASSIGN_DRIVE_LETTER ? getDriveLetter(mountResult.getStdOut()) : driveLetter);
|
||||
}
|
||||
|
||||
private boolean isVolumeMounted(String driveLetter) {
|
||||
for (Path path : FileSystems.getDefault().getRootDirectories()) {
|
||||
if (path.toString().startsWith(driveLetter)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private CommandResult bypassProxyAndRetryMount(Script localhostMountScript, Script ipv6literalMountScript, Script proxyBypassScript) throws CommandFailedException {
|
||||
CommandFailedException latestException = null;
|
||||
for (int i = 0; i < MAX_MOUNT_ATTEMPTS; i++) {
|
||||
@@ -117,13 +101,42 @@ public final class WindowsWebDavMounter implements WebDavMounterStrategy {
|
||||
throw latestException;
|
||||
}
|
||||
|
||||
private String getDriveLetter(String result) throws CommandFailedException {
|
||||
private Character getDriveLetter(String result) throws CommandFailedException {
|
||||
final Matcher matcher = WIN_MOUNT_DRIVELETTER_PATTERN.matcher(result);
|
||||
if (matcher.find()) {
|
||||
return matcher.group(1);
|
||||
return CharUtils.toCharacterObject(matcher.group(1));
|
||||
} else {
|
||||
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:
|
||||
if (isVolumeMounted()) {
|
||||
unmountScript.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reveal() throws CommandFailedException {
|
||||
openExplorerScript.execute();
|
||||
}
|
||||
|
||||
private boolean isVolumeMounted() {
|
||||
return driveLetters.getOccupiedDriveLetters().contains(driveLetter);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -325,6 +325,32 @@
|
||||
-fx-background-color: black;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* ChoiceBox *
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
.choice-box {
|
||||
-fx-background-color: COLOR_BORDER, linear-gradient(to bottom, #F0F0F0 0%, #E5E5E5 100%);
|
||||
-fx-background-insets: 0, 1;
|
||||
-fx-background-radius: 0, 0;
|
||||
-fx-padding: 0.1em 0.6em 0.1em 0.6em;
|
||||
-fx-text-fill: COLOR_TEXT;
|
||||
}
|
||||
|
||||
.choice-box > .open-button > .arrow {
|
||||
-fx-background-color: transparent, COLOR_TEXT;
|
||||
-fx-background-insets: 0 0 -1 0, 0;
|
||||
-fx-padding: 0.166667em 0.333333em 0.166667em 0.333333em; /* 2 4 2 4 */
|
||||
-fx-shape: "M 0 0 h 7 l -3.5 4 z";
|
||||
}
|
||||
|
||||
.choice-box .context-menu {
|
||||
-fx-background-color: COLOR_BORDER, #FFF;
|
||||
-fx-background-insets: 0, 1;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* *
|
||||
* ProgressIndicator *
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
<?import javafx.scene.text.Text?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.control.Separator?>
|
||||
<?import javafx.scene.control.ChoiceBox?>
|
||||
|
||||
<GridPane vgap="12.0" hgap="12.0" prefWidth="400.0" xmlns:fx="http://javafx.com/fxml">
|
||||
<padding>
|
||||
@@ -69,6 +70,10 @@
|
||||
<!-- Row 3.1 -->
|
||||
<Label text="%unlock.label.mountName" GridPane.rowIndex="1" GridPane.columnIndex="0" />
|
||||
<TextField fx:id="mountName" GridPane.rowIndex="1" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" />
|
||||
|
||||
<!-- Row 3.2 -->
|
||||
<Label text="%unlock.label.winDriveLetter" GridPane.rowIndex="2" GridPane.columnIndex="0" />
|
||||
<ChoiceBox fx:id="winDriveLetter" GridPane.rowIndex="2" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" />
|
||||
</GridPane>
|
||||
|
||||
<!-- Row 4 -->
|
||||
|
||||
@@ -30,11 +30,13 @@ initialize.button.ok=Create vault
|
||||
# unlock.fxml
|
||||
unlock.label.password=Password
|
||||
unlock.label.mountName=Drive name
|
||||
unlock.label.winDriveLetter=Drive letter
|
||||
unlock.label.downloadsPageLink=All Cryptomator versions
|
||||
unlock.label.advancedHeading=Advanced options
|
||||
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.decryptionFailed=Decryption failed.
|
||||
unlock.errorMessage.unsupportedKeyLengthInstallJCE=Decryption failed. Please install Oracle JCE Unlimited Strength Policy.
|
||||
|
||||
Reference in New Issue
Block a user