some Windows WebDAV compatibility fixes

This commit is contained in:
Sebastian Stenzel
2016-02-29 12:25:24 +01:00
parent be4dab2773
commit a6c99c273e
6 changed files with 200 additions and 5 deletions

View File

@@ -16,7 +16,6 @@ import java.util.concurrent.LinkedBlockingQueue;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.filesystem.Folder;
import org.cryptomator.frontend.Frontend;
import org.cryptomator.frontend.FrontendCreationFailedException;
@@ -36,7 +35,6 @@ import org.slf4j.LoggerFactory;
public class WebDavServer implements FrontendFactory {
private static final Logger LOG = LoggerFactory.getLogger(WebDavServer.class);
private static final String LOCALHOST = SystemUtils.IS_OS_WINDOWS ? "::1" : "localhost";
private static final int MAX_PENDING_REQUESTS = 200;
private static final int MAX_THREADS = 200;
private static final int MIN_THREADS = 4;
@@ -57,8 +55,8 @@ public class WebDavServer implements FrontendFactory {
this.servletCollection = new ContextHandlerCollection();
this.servletContextFactory = servletContextFactory;
this.webdavMounterProvider = webdavMounterProvider;
localConnector.setHost(LOCALHOST);
servletCollection.addHandler(WindowsCompatibilityServlet.createServletContextHandler());
server.setConnectors(new Connector[] {localConnector});
server.setHandler(servletCollection);
}
@@ -111,7 +109,7 @@ public class WebDavServer implements FrontendFactory {
}
final URI uri;
try {
uri = new URI("http", null, LOCALHOST, getPort(), contextPath, null, null);
uri = new URI("http", null, "localhost", getPort(), contextPath, null, null);
} catch (URISyntaxException e) {
throw new IllegalStateException(e);
}

View File

@@ -19,6 +19,7 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.filesystem.Folder;
import org.cryptomator.frontend.webdav.filters.AcceptRangeFilter;
import org.cryptomator.frontend.webdav.filters.LoopbackFilter;
import org.cryptomator.frontend.webdav.filters.MacChunkedPutCompatibilityFilter;
import org.cryptomator.frontend.webdav.filters.MkcolComplianceFilter;
import org.cryptomator.frontend.webdav.filters.UriNormalizationFilter;
@@ -65,6 +66,7 @@ class WebDavServletContextFactory {
final ServletContextHandler servletContext = new ServletContextHandler(null, contextPath, ServletContextHandler.SESSIONS);
final ServletHolder servletHolder = new ServletHolder(contextPath, new WebDavServlet(contextRoot, root));
servletContext.addServlet(servletHolder, WILDCARD);
servletContext.addFilter(LoopbackFilter.class, WILDCARD, EnumSet.of(DispatcherType.REQUEST));
servletContext.addFilter(MkcolComplianceFilter.class, WILDCARD, EnumSet.of(DispatcherType.REQUEST));
servletContext.addFilter(AcceptRangeFilter.class, WILDCARD, EnumSet.of(DispatcherType.REQUEST));
servletContext.addFilter(new FilterHolder(new UriNormalizationFilter(resourceTypeChecker)), WILDCARD, EnumSet.of(DispatcherType.REQUEST));

View File

@@ -0,0 +1,39 @@
package org.cryptomator.frontend.webdav;
import java.io.IOException;
import java.util.EnumSet;
import javax.servlet.DispatcherType;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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 {
private static final String ROOT_PATH = "/";
@Override
protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.addHeader("DAV", "1, 2");
resp.addHeader("MS-Author-Via", "DAV");
// resp.addHeader("Allow", "OPTIONS, GET, HEAD, POST, TRACE, PROPFIND, PROPPATCH, MKCOL, COPY, PUT, DELETE, MOVE, LOCK, UNLOCK");
resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
}
public static ServletContextHandler createServletContextHandler() {
final ServletContextHandler servletContext = new ServletContextHandler(null, ROOT_PATH, ServletContextHandler.NO_SESSIONS);
final ServletHolder servletHolder = new ServletHolder(ROOT_PATH, WindowsCompatibilityServlet.class);
servletContext.addServlet(servletHolder, ROOT_PATH);
servletContext.addFilter(LoopbackFilter.class, ROOT_PATH, EnumSet.of(DispatcherType.REQUEST));
return servletContext;
}
}

View File

@@ -0,0 +1,37 @@
package org.cryptomator.frontend.webdav.filters;
import java.io.IOException;
import java.net.InetAddress;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Blocks all requests from external hosts.
*/
public class LoopbackFilter implements HttpFilter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// no-op
}
@Override
public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (InetAddress.getByName(request.getRemoteAddr()).isLoopbackAddress()) {
chain.doFilter(request, response);
} else {
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "Can only access drive from localhost.");
}
}
@Override
public void destroy() {
// no-op
}
}

View File

@@ -0,0 +1,49 @@
/*******************************************************************************
* Copyright (c) 2016 Sebastian Stenzel and others.
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
*******************************************************************************/
package org.cryptomator.frontend.webdav;
import java.io.IOException;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
public class WindowsCompatibilityServletTest {
@Test
public void testFactory() throws ServletException {
ServletHolder[] holders = WindowsCompatibilityServlet.createServletContextHandler().getServletHandler().getServlets();
Assert.assertEquals(1, holders.length);
ServletHolder holder = holders[0];
Servlet servlet = holder.getServlet();
Assert.assertTrue(servlet instanceof WindowsCompatibilityServlet);
}
@Test
public void testResponse() throws IOException, ServletException {
final WindowsCompatibilityServlet servlet = new WindowsCompatibilityServlet();
final HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
final HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
servlet.doOptions(request, response);
Mockito.verify(response).addHeader("MS-Author-Via", "DAV");
Mockito.verify(response).addHeader("DAV", "1, 2");
Mockito.verify(response).setStatus(204);
}
}

View File

@@ -0,0 +1,70 @@
/*******************************************************************************
* Copyright (c) 2016 Sebastian Stenzel and others.
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
*******************************************************************************/
package org.cryptomator.frontend.webdav.filters;
import java.io.IOException;
import java.net.InetAddress;
import java.util.Arrays;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
@RunWith(Theories.class)
public class LoopbackFilterTest {
@DataPoints
public static final Iterable<String> HOST_NAMES = Arrays.asList("127.0.0.1", "0::1", "1.2.3.4", "google.com");
private LoopbackFilter filter;
private FilterChain chain;
private HttpServletRequest request;
private HttpServletResponse response;
@Before
public void setup() {
filter = new LoopbackFilter();
chain = Mockito.mock(FilterChain.class);
request = Mockito.mock(HttpServletRequest.class);
response = Mockito.mock(HttpServletResponse.class);
}
@Theory
public void testWithLoopbackAddress(String hostname) throws IOException, ServletException {
Assume.assumeTrue(InetAddress.getByName(hostname).isLoopbackAddress());
Mockito.when(request.getRemoteAddr()).thenReturn(hostname);
filter.doFilter(request, response, chain);
Mockito.verify(chain).doFilter(request, response);
}
@Theory
public void testWithExternalAddress(String hostname) throws IOException, ServletException {
Assume.assumeFalse(InetAddress.getByName(hostname).isLoopbackAddress());
Mockito.when(request.getRemoteAddr()).thenReturn(hostname);
filter.doFilter(request, response, chain);
ArgumentCaptor<Integer> statusCode = ArgumentCaptor.forClass(Integer.class);
Mockito.verify(response).sendError(statusCode.capture(), Mockito.anyString());
Assert.assertEquals(405, statusCode.getValue().intValue());
}
}