diff --git a/main/frontend-api/src/main/java/org/cryptomator/frontend/FrontendFactory.java b/main/frontend-api/src/main/java/org/cryptomator/frontend/FrontendFactory.java index 9401355d0..37fb053cd 100644 --- a/main/frontend-api/src/main/java/org/cryptomator/frontend/FrontendFactory.java +++ b/main/frontend-api/src/main/java/org/cryptomator/frontend/FrontendFactory.java @@ -16,10 +16,11 @@ 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. + * @param id unique id of the frontend, i.e. used to generate a unique uri + * @param name Name of the frontend, i.e. used to generate a readable/recognizable name of a common virtual drive * @return A new frontend * @throws FrontendCreationFailedException If creation was not possible. */ - Frontend create(Folder root, String uniqueName) throws FrontendCreationFailedException; + Frontend create(Folder root, FrontendId id, String name) throws FrontendCreationFailedException; } diff --git a/main/frontend-api/src/main/java/org/cryptomator/frontend/FrontendId.java b/main/frontend-api/src/main/java/org/cryptomator/frontend/FrontendId.java new file mode 100644 index 000000000..97915c074 --- /dev/null +++ b/main/frontend-api/src/main/java/org/cryptomator/frontend/FrontendId.java @@ -0,0 +1,83 @@ +package org.cryptomator.frontend; + +import static java.util.UUID.randomUUID; + +import java.nio.ByteBuffer; +import java.util.Base64; +import java.util.UUID; + +public class FrontendId { + + public static final String FRONTEND_ID_PATTERN = "[a-zA-Z0-9_-]{12}"; + + public static FrontendId generate() { + return new FrontendId(); + } + + public static FrontendId from(String value) { + return new FrontendId(value); + } + + private final String value; + + private FrontendId() { + this(generateId()); + } + + private FrontendId(String value) { + if (!value.matches(FRONTEND_ID_PATTERN)) { + throw new IllegalArgumentException("Invalid frontend id " + value); + } + this.value = value; + } + + private static String generateId() { + return asBase64String(nineBytesFrom(randomUUID())); + } + + private static String asBase64String(ByteBuffer bytes) { + ByteBuffer base64Buffer = Base64.getUrlEncoder().encode(bytes); + return new String(asByteArray(base64Buffer)); + } + + private static ByteBuffer nineBytesFrom(UUID uuid) { + ByteBuffer uuidBuffer = ByteBuffer.allocate(9); + uuidBuffer.putLong(uuid.getMostSignificantBits()); + uuidBuffer.put((byte) (uuid.getLeastSignificantBits() & 0xFF)); + uuidBuffer.flip(); + return uuidBuffer; + } + + private static byte[] asByteArray(ByteBuffer buffer) { + if (buffer.hasArray()) { + return buffer.array(); + } else { + byte[] bytes = new byte[buffer.remaining()]; + buffer.get(bytes); + return bytes; + } + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != getClass()) { + return false; + } + return obj == this || internalEquals((FrontendId) obj); + } + + private boolean internalEquals(FrontendId obj) { + return value.equals(obj.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public String toString() { + return value; + } + +} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/ContextPathBuilder.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/ContextPathBuilder.java new file mode 100644 index 000000000..ee036d221 --- /dev/null +++ b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/ContextPathBuilder.java @@ -0,0 +1,30 @@ +package org.cryptomator.frontend.webdav; + +import static java.lang.String.format; +import static org.cryptomator.frontend.FrontendId.FRONTEND_ID_PATTERN; + +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.cryptomator.frontend.FrontendId; + +class ContextPaths { + + private static final Pattern SERVLET_PATH_WITH_FRONTEND_ID_PATTERN = Pattern.compile("^/(" + FRONTEND_ID_PATTERN + ")(/.*)?$"); + private static final int FRONTEND_ID_GROUP = 1; + + public static String from(FrontendId id, String name) { + return format("/%s/%s", id, name); + } + + public static Optional extractFrontendId(String path) { + Matcher matcher = SERVLET_PATH_WITH_FRONTEND_ID_PATTERN.matcher(path); + if (matcher.matches()) { + return Optional.of(FrontendId.from(matcher.group(FRONTEND_ID_GROUP))); + } else { + return Optional.empty(); + } + } + +} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/Tarpit.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/Tarpit.java index bedccc780..42226b3db 100644 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/Tarpit.java +++ b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/Tarpit.java @@ -8,24 +8,50 @@ package org.cryptomator.frontend.webdav; import static java.lang.Math.max; import static java.lang.System.currentTimeMillis; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + import javax.inject.Inject; import javax.inject.Singleton; import javax.servlet.http.HttpServletRequest; +import org.cryptomator.frontend.FrontendId; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + @Singleton class Tarpit { - + + private static final Logger LOG = LoggerFactory.getLogger(Tarpit.class); private static final long DELAY_MS = 10000; - + + private final Set validFrontendIds = new HashSet<>(); + @Inject - public Tarpit() {} - + public Tarpit() { + } + + public void register(FrontendId frontendId) { + validFrontendIds.add(frontendId); + } + + public void unregister(FrontendId frontendId) { + validFrontendIds.remove(frontendId); + } + public void handle(HttpServletRequest req) { - if (isRequestWithVaultId(req)) { + if (isRequestWithInvalidVaultId(req)) { delayExecutionUninterruptibly(); + LOG.debug("Delayed request to " + req.getRequestURI() + " by " + DELAY_MS + "ms"); } } + private boolean isRequestWithInvalidVaultId(HttpServletRequest req) { + Optional frontendId = ContextPaths.extractFrontendId(req.getServletPath()); + return frontendId.isPresent() && !isValid(frontendId.get()); + } + private void delayExecutionUninterruptibly() { long expected = currentTimeMillis() + DELAY_MS; long sleepTime = DELAY_MS; @@ -38,9 +64,8 @@ class Tarpit { } } - private boolean isRequestWithVaultId(HttpServletRequest req) { - String path = req.getServletPath(); - return path.matches("^/[a-zA-Z0-9_-]{12}/.*$"); + private boolean isValid(FrontendId frontendId) { + return validFrontendIds.contains(frontendId); } } diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavFrontend.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavFrontend.java index 50081c846..a657cc376 100644 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavFrontend.java +++ b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavFrontend.java @@ -24,12 +24,15 @@ class WebDavFrontend implements Frontend { private final WebDavMounterProvider webdavMounterProvider; private final ServletContextHandler handler; private final URI uri; + private final Runnable afterClose; + private WebDavMount mount; - public WebDavFrontend(WebDavMounterProvider webdavMounterProvider, ServletContextHandler handler, URI uri) throws FrontendCreationFailedException { + public WebDavFrontend(WebDavMounterProvider webdavMounterProvider, ServletContextHandler handler, URI uri, Runnable afterUnmount) throws FrontendCreationFailedException { this.webdavMounterProvider = webdavMounterProvider; this.handler = handler; this.uri = uri; + this.afterClose = afterUnmount; try { handler.start(); } catch (Exception e) { @@ -39,8 +42,12 @@ class WebDavFrontend implements Frontend { @Override public void close() throws Exception { - unmount(); - handler.stop(); + try { + unmount(); + handler.stop(); + } finally { + afterClose.run(); + } } @Override diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavServer.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavServer.java index 0ec50abe3..0f18bbab5 100644 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavServer.java +++ b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavServer.java @@ -8,6 +8,8 @@ *******************************************************************************/ package org.cryptomator.frontend.webdav; +import static java.lang.String.format; + import java.net.URI; import java.net.URISyntaxException; import java.util.concurrent.BlockingQueue; @@ -20,6 +22,7 @@ import org.cryptomator.filesystem.Folder; import org.cryptomator.frontend.Frontend; import org.cryptomator.frontend.FrontendCreationFailedException; import org.cryptomator.frontend.FrontendFactory; +import org.cryptomator.frontend.FrontendId; import org.cryptomator.frontend.webdav.mount.WebDavMounterProvider; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Server; @@ -45,9 +48,10 @@ public class WebDavServer implements FrontendFactory { private final ContextHandlerCollection servletCollection; private final WebDavServletContextFactory servletContextFactory; private final WebDavMounterProvider webdavMounterProvider; + private final Tarpit tarpit; @Inject - WebDavServer(WebDavServletContextFactory servletContextFactory, WebDavMounterProvider webdavMounterProvider, DefaultServlet defaultServlet) { + WebDavServer(WebDavServletContextFactory servletContextFactory, WebDavMounterProvider webdavMounterProvider, DefaultServlet defaultServlet, Tarpit tarpit) { final BlockingQueue queue = new LinkedBlockingQueue<>(MAX_PENDING_REQUESTS); final ThreadPool tp = new QueuedThreadPool(MAX_THREADS, MIN_THREADS, THREAD_IDLE_SECONDS, queue); this.server = new Server(tp); @@ -55,7 +59,8 @@ public class WebDavServer implements FrontendFactory { this.servletCollection = new ContextHandlerCollection(); this.servletContextFactory = servletContextFactory; this.webdavMounterProvider = webdavMounterProvider; - + this.tarpit = tarpit; + servletCollection.addHandler(defaultServlet.createServletContextHandler()); server.setConnectors(new Connector[] {localConnector}); server.setHandler(servletCollection); @@ -103,10 +108,8 @@ public class WebDavServer implements FrontendFactory { } @Override - public Frontend create(Folder root, String contextPath) throws FrontendCreationFailedException { - if (!contextPath.startsWith("/")) { - throw new IllegalArgumentException("contextPath must begin with '/'"); - } + public Frontend create(Folder root, FrontendId id, String name) throws FrontendCreationFailedException { + String contextPath = format("/%s/%s", id, name); final URI uri; try { uri = new URI("http", null, "localhost", getPort(), contextPath, null, null); @@ -114,8 +117,9 @@ public class WebDavServer implements FrontendFactory { throw new IllegalStateException(e); } final ServletContextHandler handler = addServlet(root, uri); + tarpit.register(id); LOG.info("Servlet available under " + uri); - return new WebDavFrontend(webdavMounterProvider, handler, uri); + return new WebDavFrontend(webdavMounterProvider, handler, uri, () -> tarpit.unregister(id)); } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java b/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java index f4e94a214..3647e8054 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java @@ -42,6 +42,7 @@ import org.cryptomator.frontend.Frontend; import org.cryptomator.frontend.Frontend.MountParam; import org.cryptomator.frontend.FrontendCreationFailedException; import org.cryptomator.frontend.FrontendFactory; +import org.cryptomator.frontend.FrontendId; import org.cryptomator.ui.settings.Settings; import org.cryptomator.ui.util.DeferredClosable; import org.cryptomator.ui.util.DeferredCloser; @@ -73,7 +74,7 @@ public class Vault implements CryptoFileSystemDelegate { private final Set whitelistedResourcesWithInvalidMac = new HashSet<>(); private final AtomicReference nioFileSystem = new AtomicReference<>(); private final String id; - + private String mountName; private Character winDriveLetter; private Optional statsFileSystem = Optional.empty(); @@ -81,7 +82,8 @@ public class Vault implements CryptoFileSystemDelegate { /** * Package private constructor, use {@link VaultFactory}. - * @param string + * + * @param string */ Vault(String id, Path vaultDirectoryPath, ShorteningFileSystemFactory shorteningFileSystemFactory, CryptoFileSystemFactory cryptoFileSystemFactory, DeferredCloser closer) { this.path = new SimpleObjectProperty(vaultDirectoryPath); @@ -133,7 +135,7 @@ public class Vault implements CryptoFileSystemDelegate { FileSystem normalizingFs = new NormalizedNameFileSystem(cryptoFs, SystemUtils.IS_OS_MAC_OSX ? Form.NFD : Form.NFC); StatsFileSystem statsFs = new StatsFileSystem(normalizingFs); statsFileSystem = Optional.of(statsFs); - Frontend frontend = frontendFactory.create(statsFs, contextPath()); + Frontend frontend = frontendFactory.create(statsFs, FrontendId.from(id), stripStart(mountName, "/")); filesystemFrontend = closer.closeLater(frontend); frontend.mount(getMountParams(settings)); success = true; @@ -146,10 +148,6 @@ public class Vault implements CryptoFileSystemDelegate { } } - private String contextPath() { - return String.format("/%s/%s", id, stripStart(mountName, "/")); - } - public synchronized void deactivateFrontend() { filesystemFrontend.close(); statsFileSystem = Optional.empty(); @@ -312,7 +310,7 @@ public class Vault implements CryptoFileSystemDelegate { public void setWinDriveLetter(Character winDriveLetter) { this.winDriveLetter = winDriveLetter; } - + public String getId() { return id; } diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/VaultFactory.java b/main/ui/src/main/java/org/cryptomator/ui/model/VaultFactory.java index 148a4317a..cf1dcc756 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/VaultFactory.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/VaultFactory.java @@ -8,18 +8,14 @@ *******************************************************************************/ package org.cryptomator.ui.model; -import static java.util.UUID.randomUUID; - -import java.nio.ByteBuffer; import java.nio.file.Path; -import java.util.Base64; -import java.util.UUID; import javax.inject.Inject; import javax.inject.Singleton; import org.cryptomator.filesystem.crypto.CryptoFileSystemFactory; import org.cryptomator.filesystem.shortening.ShorteningFileSystemFactory; +import org.cryptomator.frontend.FrontendId; import org.cryptomator.ui.util.DeferredCloser; @Singleton @@ -41,34 +37,7 @@ public class VaultFactory { } public Vault createVault(Path path) { - return createVault(generateId(), path); - } - - private String generateId() { - return asBase64String(nineBytesFrom(randomUUID())); - } - - private String asBase64String(ByteBuffer bytes) { - ByteBuffer base64Buffer = Base64.getUrlEncoder().encode(bytes); - return new String(asByteArray(base64Buffer)); - } - - private ByteBuffer nineBytesFrom(UUID uuid) { - ByteBuffer uuidBuffer = ByteBuffer.allocate(9); - uuidBuffer.putLong(uuid.getMostSignificantBits()); - uuidBuffer.put((byte)(uuid.getLeastSignificantBits() & 0xFF)); - uuidBuffer.flip(); - return uuidBuffer; - } - - private byte[] asByteArray(ByteBuffer buffer) { - if (buffer.hasArray()) { - return buffer.array(); - } else { - byte[] bytes = new byte[buffer.remaining()]; - buffer.get(bytes); - return bytes; - } + return createVault(FrontendId.generate().toString(), path); } }