diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WindowsCompatibilityServlet.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/DefaultServlet.java similarity index 70% rename from main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WindowsCompatibilityServlet.java rename to main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/DefaultServlet.java index 0edfc92f8..dfd74a572 100644 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WindowsCompatibilityServlet.java +++ b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/DefaultServlet.java @@ -11,6 +11,8 @@ package org.cryptomator.frontend.webdav; import java.io.IOException; import java.util.EnumSet; +import javax.inject.Inject; +import javax.inject.Singleton; import javax.servlet.DispatcherType; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; @@ -21,12 +23,24 @@ import org.cryptomator.frontend.webdav.filters.LoopbackFilter; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; -/** - * The server needs to respond to requests to the root resource, because Windows is stupid. - */ -public class WindowsCompatibilityServlet extends HttpServlet { +@Singleton +class DefaultServlet extends HttpServlet { private static final String ROOT_PATH = "/"; + private static final String WILDCARD = "/*"; + + private final Tarpit tarpit; + + @Inject + public DefaultServlet(Tarpit tarpit) { + this.tarpit = tarpit; + } + + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + tarpit.handle(req); + super.service(req, resp); + } @Override protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { @@ -36,11 +50,11 @@ public class WindowsCompatibilityServlet extends HttpServlet { resp.setStatus(HttpServletResponse.SC_NO_CONTENT); } - public static ServletContextHandler createServletContextHandler() { + public ServletContextHandler createServletContextHandler() { final ServletContextHandler servletContext = new ServletContextHandler(null, ROOT_PATH, ServletContextHandler.NO_SESSIONS); - final ServletHolder servletHolder = new ServletHolder(ROOT_PATH, WindowsCompatibilityServlet.class); + final ServletHolder servletHolder = new ServletHolder(ROOT_PATH, this); servletContext.addServlet(servletHolder, ROOT_PATH); - servletContext.addFilter(LoopbackFilter.class, ROOT_PATH, EnumSet.of(DispatcherType.REQUEST)); + servletContext.addFilter(LoopbackFilter.class, WILDCARD, EnumSet.of(DispatcherType.REQUEST)); return servletContext; } 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 new file mode 100644 index 000000000..bedccc780 --- /dev/null +++ b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/Tarpit.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2016 Markus Kreusch + * This file is licensed under the terms of the MIT license. + * See the LICENSE.txt file for more info. + *******************************************************************************/ +package org.cryptomator.frontend.webdav; + +import static java.lang.Math.max; +import static java.lang.System.currentTimeMillis; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.servlet.http.HttpServletRequest; + +@Singleton +class Tarpit { + + private static final long DELAY_MS = 10000; + + @Inject + public Tarpit() {} + + public void handle(HttpServletRequest req) { + if (isRequestWithVaultId(req)) { + delayExecutionUninterruptibly(); + } + } + + private void delayExecutionUninterruptibly() { + long expected = currentTimeMillis() + DELAY_MS; + long sleepTime = DELAY_MS; + while (expected > currentTimeMillis()) { + try { + Thread.sleep(sleepTime); + } catch (InterruptedException e) { + sleepTime = max(0, currentTimeMillis() - expected + 10); + } + } + } + + private boolean isRequestWithVaultId(HttpServletRequest req) { + String path = req.getServletPath(); + return path.matches("^/[a-zA-Z0-9_-]{12}/.*$"); + } + +} 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 1b9076ea3..0ec50abe3 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 @@ -47,7 +47,7 @@ public class WebDavServer implements FrontendFactory { private final WebDavMounterProvider webdavMounterProvider; @Inject - WebDavServer(WebDavServletContextFactory servletContextFactory, WebDavMounterProvider webdavMounterProvider) { + WebDavServer(WebDavServletContextFactory servletContextFactory, WebDavMounterProvider webdavMounterProvider, DefaultServlet defaultServlet) { 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); @@ -56,7 +56,7 @@ public class WebDavServer implements FrontendFactory { this.servletContextFactory = servletContextFactory; this.webdavMounterProvider = webdavMounterProvider; - servletCollection.addHandler(WindowsCompatibilityServlet.createServletContextHandler()); + servletCollection.addHandler(defaultServlet.createServletContextHandler()); server.setConnectors(new Connector[] {localConnector}); server.setHandler(servletCollection); } diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavServletContextFactory.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavServletContextFactory.java index 5080c7a27..85915d192 100644 --- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavServletContextFactory.java +++ b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavServletContextFactory.java @@ -35,10 +35,9 @@ import org.eclipse.jetty.servlet.ServletHolder; class WebDavServletContextFactory { private static final String WILDCARD = "/*"; - + @Inject - public WebDavServletContextFactory() { - } + public WebDavServletContextFactory() {} /** * Creates a new Jetty ServletContextHandler, that can be be added to a servletCollection as follows: diff --git a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/WindowsCompatibilityServletTest.java b/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/DefaultServletTest.java similarity index 78% rename from main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/WindowsCompatibilityServletTest.java rename to main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/DefaultServletTest.java index 4a5feaf9d..5d87a9b2b 100644 --- a/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/WindowsCompatibilityServletTest.java +++ b/main/frontend-webdav/src/test/java/org/cryptomator/frontend/webdav/DefaultServletTest.java @@ -8,6 +8,8 @@ *******************************************************************************/ package org.cryptomator.frontend.webdav; +import static org.mockito.Mockito.mock; + import java.io.IOException; import javax.servlet.Servlet; @@ -21,21 +23,26 @@ import org.junit.Test; import org.mockito.Mockito; -public class WindowsCompatibilityServletTest { +public class DefaultServletTest { + + private Tarpit tarpit = mock(Tarpit.class); + + private DefaultServlet inTest = new DefaultServlet(tarpit); @Test public void testFactory() throws ServletException { - ServletHolder[] holders = WindowsCompatibilityServlet.createServletContextHandler().getServletHandler().getServlets(); + + ServletHolder[] holders = inTest.createServletContextHandler().getServletHandler().getServlets(); Assert.assertEquals(1, holders.length); ServletHolder holder = holders[0]; Servlet servlet = holder.getServlet(); - Assert.assertTrue(servlet instanceof WindowsCompatibilityServlet); + Assert.assertTrue(servlet instanceof DefaultServlet); } @Test public void testResponse() throws IOException, ServletException { - final WindowsCompatibilityServlet servlet = new WindowsCompatibilityServlet(); + final DefaultServlet servlet = inTest; final HttpServletRequest request = Mockito.mock(HttpServletRequest.class); final HttpServletResponse response = Mockito.mock(HttpServletResponse.class);