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

This commit is contained in:
Markus Kreusch
2016-08-12 15:11:54 +02:00
parent 24df3c3809
commit 28f275c22d
8 changed files with 178 additions and 61 deletions

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

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

View File

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

View File

@@ -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

View File

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

View File

@@ -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;
}

View File

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