Skip to content

Commit

Permalink
some Windows WebDAV compatibility fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
Sebastian Stenzel committed Feb 29, 2016
1 parent be4dab2 commit a6c99c2
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
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;
Expand All @@ -36,7 +35,6 @@
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;
Expand All @@ -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);
}
Expand Down Expand Up @@ -111,7 +109,7 @@ public Frontend create(Folder root, String contextPath) throws FrontendCreationF
}
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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
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;
Expand Down Expand Up @@ -65,6 +66,7 @@ public ServletContextHandler create(URI contextRoot, Folder root) {
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));
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}

}
Original file line number Diff line number Diff line change
@@ -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
}

}
Original file line number Diff line number Diff line change
@@ -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);
}

}
Original file line number Diff line number Diff line change
@@ -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());
}

}

0 comments on commit a6c99c2

Please sign in to comment.