Browse Source

Requests on parent folders of valid vault urls no longer get delayed

Markus Kreusch 9 năm trước cách đây
mục cha
commit
28f275c22d

+ 3 - 2
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;
 
 }

+ 83 - 0
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;
+	}
+
+}

+ 30 - 0
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<FrontendId> 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();
+		}
+	}
+
+}

+ 33 - 8
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<FrontendId> 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> 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);
 	}
 
 }

+ 10 - 3
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

+ 11 - 7
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<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);
@@ -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));
 	}
 
 }

+ 6 - 8
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<String> whitelistedResourcesWithInvalidMac = new HashSet<>();
 	private final AtomicReference<FileSystem> nioFileSystem = new AtomicReference<>();
 	private final String id;
-	
+
 	private String mountName;
 	private Character winDriveLetter;
 	private Optional<StatsFileSystem> 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<Path>(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;
 	}

+ 2 - 33
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);
 	}
 
 }