renamed module

This commit is contained in:
Sebastian Stenzel
2016-01-25 14:30:51 +01:00
parent 56b061206a
commit 8d7c3a8f7b
31 changed files with 98 additions and 54 deletions
@@ -0,0 +1,60 @@
package org.cryptomator.filesystem.jackrabbit;
import java.io.UncheckedIOException;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.jackrabbit.webdav.DavLocatorFactory;
import org.cryptomator.filesystem.File;
import org.cryptomator.filesystem.delegating.DelegatingFile;
import org.cryptomator.filesystem.delegating.DelegatingReadableFile;
import org.cryptomator.filesystem.delegating.DelegatingWritableFile;
public class FileLocator extends DelegatingFile<DelegatingReadableFile, DelegatingWritableFile, FolderLocator>implements FileSystemResourceLocator {
private final DavLocatorFactory factory;
private final String prefix;
private final AtomicReference<String> resourcePath = new AtomicReference<>();
public FileLocator(DavLocatorFactory factory, String prefix, FolderLocator parent, File delegate) {
super(parent, delegate);
this.factory = factory;
this.prefix = prefix;
}
@Override
public DelegatingReadableFile openReadable() throws UncheckedIOException {
return new DelegatingReadableFile(delegate.openReadable());
}
@Override
public DelegatingWritableFile openWritable() throws UncheckedIOException {
return new DelegatingWritableFile(delegate.openWritable());
}
@Override
public String getPrefix() {
return prefix;
}
@Override
public boolean isRootLocation() {
return false;
}
@Override
public DavLocatorFactory getFactory() {
return factory;
}
@Override
public AtomicReference<String> getResourcePathRef() {
return resourcePath;
}
@Override
public String computeResourcePath() {
assert parent().isPresent();
return parent().get().getResourcePath() + name();
}
}
@@ -0,0 +1,23 @@
package org.cryptomator.filesystem.jackrabbit;
import org.apache.jackrabbit.webdav.DavLocatorFactory;
import org.cryptomator.filesystem.FileSystem;
import org.cryptomator.filesystem.Folder;
class FileSystemLocator extends FolderLocator implements FileSystem {
public FileSystemLocator(DavLocatorFactory factory, String prefix, Folder delegate) {
super(factory, prefix, null, delegate);
}
@Override
public boolean isRootLocation() {
return true;
}
@Override
public String getResourcePath() {
return "/";
}
}
@@ -0,0 +1,61 @@
package org.cryptomator.filesystem.jackrabbit;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.jackrabbit.webdav.DavResourceLocator;
import org.apache.jackrabbit.webdav.util.EncodeUtil;
import org.cryptomator.common.LazyInitializer;
import org.cryptomator.filesystem.Node;
public interface FileSystemResourceLocator extends DavResourceLocator, Node {
@Override
default String getResourcePath() {
return LazyInitializer.initializeLazily(getResourcePathRef(), this::computeResourcePath);
}
AtomicReference<String> getResourcePathRef();
String computeResourcePath();
@Override
Optional<FolderLocator> parent();
@Override
default String getWorkspacePath() {
return null;
}
@Override
default String getWorkspaceName() {
return null;
}
@Override
default boolean isSameWorkspace(DavResourceLocator locator) {
return false;
}
@Override
default boolean isSameWorkspace(String workspaceName) {
return false;
}
default String getHref() {
final boolean isCollection = getResourcePath().endsWith("/");
return getHref(isCollection);
}
@Override
default String getHref(boolean isCollection) {
final String encodedResourcePath = EncodeUtil.escapePath(getResourcePath());
return getPrefix() + encodedResourcePath;
}
@Override
default String getRepositoryPath() {
return getResourcePath();
}
}
@@ -0,0 +1,48 @@
package org.cryptomator.filesystem.jackrabbit;
import java.net.URI;
import org.apache.commons.lang3.StringUtils;
import org.apache.jackrabbit.webdav.DavLocatorFactory;
import org.apache.jackrabbit.webdav.util.EncodeUtil;
import org.cryptomator.filesystem.Folder;
public class FileSystemResourceLocatorFactory implements DavLocatorFactory {
private final FileSystemLocator fs;
public FileSystemResourceLocatorFactory(URI contextRootUri, Folder root) {
String pathPrefix = StringUtils.removeEnd(contextRootUri.toString(), "/");
this.fs = new FileSystemLocator(this, pathPrefix, root);
}
@Override
public FileSystemResourceLocator createResourceLocator(String prefix, String href) {
final String fullPrefix = StringUtils.removeEnd(prefix, "/");
final String remainingHref = StringUtils.removeStart(href, fullPrefix);
final String unencodedRemaingingHref = EncodeUtil.unescape(remainingHref);
return createResourceLocator(unencodedRemaingingHref);
}
@Override
public FileSystemResourceLocator createResourceLocator(String prefix, String workspacePath, String resourcePath) {
return createResourceLocator(resourcePath);
}
@Override
public FileSystemResourceLocator createResourceLocator(String prefix, String workspacePath, String path, boolean isResourcePath) {
return createResourceLocator(path);
}
private FileSystemResourceLocator createResourceLocator(String path) {
if (StringUtils.isEmpty(path) || "/".equals(path)) {
return fs;
} else if (path.endsWith("/")) {
return fs.resolveFolder(path);
} else {
return fs.resolveFile(path);
}
}
}
@@ -0,0 +1,71 @@
package org.cryptomator.filesystem.jackrabbit;
import java.io.UncheckedIOException;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.jackrabbit.webdav.DavLocatorFactory;
import org.cryptomator.filesystem.File;
import org.cryptomator.filesystem.Folder;
import org.cryptomator.filesystem.delegating.DelegatingFolder;
import org.cryptomator.filesystem.delegating.DelegatingReadableFile;
import org.cryptomator.filesystem.delegating.DelegatingWritableFile;
public class FolderLocator extends DelegatingFolder<DelegatingReadableFile, DelegatingWritableFile, FolderLocator, FileLocator>implements FileSystemResourceLocator {
private final DavLocatorFactory factory;
private final String prefix;
private final AtomicReference<String> resourcePath = new AtomicReference<>();
public FolderLocator(DavLocatorFactory factory, String prefix, FolderLocator parent, Folder delegate) {
super(parent, delegate);
this.factory = factory;
this.prefix = prefix;
}
@Override
protected FileLocator newFile(File delegate) {
return new FileLocator(factory, prefix, this, delegate);
}
@Override
public FileLocator resolveFile(String relativePath) throws UncheckedIOException {
return (FileLocator) super.resolveFile(relativePath);
}
@Override
protected FolderLocator newFolder(Folder delegate) {
return new FolderLocator(factory, prefix, this, delegate);
}
@Override
public FolderLocator resolveFolder(String relativePath) throws UncheckedIOException {
return (FolderLocator) super.resolveFolder(relativePath);
}
@Override
public String getPrefix() {
return prefix;
}
@Override
public boolean isRootLocation() {
return false;
}
@Override
public DavLocatorFactory getFactory() {
return factory;
}
@Override
public AtomicReference<String> getResourcePathRef() {
return resourcePath;
}
@Override
public String computeResourcePath() {
assert parent().isPresent();
return parent().get().getResourcePath() + name() + "/";
}
}
@@ -0,0 +1,38 @@
package org.cryptomator.webdav.filters;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Adds an <code>Accept-Range: bytes</code> header to all <code>GET</code> requests.
*/
public class AcceptRangeFilter implements HttpFilter {
private static final String METHOD_GET = "GET";
private static final String HEADER_ACCEPT_RANGES = "Accept-Ranges";
private static final String HEADER_ACCEPT_RANGE_VALUE = "bytes";
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// no-op
}
@Override
public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
if (METHOD_GET.equalsIgnoreCase(request.getMethod())) {
response.addHeader(HEADER_ACCEPT_RANGES, HEADER_ACCEPT_RANGE_VALUE);
}
chain.doFilter(request, response);
}
@Override
public void destroy() {
// no-op
}
}
@@ -0,0 +1,26 @@
package org.cryptomator.webdav.filters;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
interface HttpFilter extends Filter {
@Override
default void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
doFilterHttp((HttpServletRequest) request, (HttpServletResponse) response, chain);
} else {
chain.doFilter(request, response);
}
}
void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException;
}
@@ -0,0 +1,146 @@
package org.cryptomator.webdav.filters;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.input.BoundedInputStream;
/**
* If a PUT request with chunked transfer encoding and a X-Expected-Entity-Length header field is sent,
* the input stream will return EOF after the number of bytes stated in this header has been read.
*
* This filter ensures compatibility of the Mac OS X WebDAV client, as Macs don't terminate chunked transfers normally (by sending a 0-byte-chunk).
*/
public class MacChunkedPutCompatibilityFilter implements HttpFilter {
private static final String METHOD_PUT = "PUT";
private static final String HEADER_TRANSFER_ENCODING = "Transfer-Encoding";
private static final String HEADER_X_EXPECTED_ENTITIY_LENGTH = "X-Expected-Entity-Length";
private static final String HEADER_TRANSFER_ENCODING_CHUNKED = "chunked";
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// no-op
}
@Override
public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
final String expectedEntitiyLengthHeader = request.getHeader(HEADER_X_EXPECTED_ENTITIY_LENGTH);
if (METHOD_PUT.equalsIgnoreCase(request.getMethod()) //
&& HEADER_TRANSFER_ENCODING_CHUNKED.equalsIgnoreCase(request.getHeader(HEADER_TRANSFER_ENCODING)) //
&& expectedEntitiyLengthHeader != null) {
long expectedEntitiyLength;
try {
expectedEntitiyLength = Long.valueOf(expectedEntitiyLengthHeader);
} catch (NumberFormatException e) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid X-Expected-Entity-Length");
return;
}
chain.doFilter(new PutRequestWithBoundedInputStream(request, expectedEntitiyLength), response);
} else {
chain.doFilter(request, response);
}
}
@Override
public void destroy() {
// no-op
}
private static class PutRequestWithBoundedInputStream extends HttpServletRequestWrapper {
private final long inputStreamLimit;
public PutRequestWithBoundedInputStream(HttpServletRequest request, long inputStreamLimit) {
super(request);
this.inputStreamLimit = inputStreamLimit;
}
@Override
public ServletInputStream getInputStream() throws IOException {
return new BoundedServletInputStream(super.getInputStream(), inputStreamLimit);
}
}
/**
* Like {@link BoundedInputStream}, but as a ServletInputStream.
*/
private static class BoundedServletInputStream extends ServletInputStream {
private final BoundedInputStream boundedIn;
private final ServletInputStream servletIn;
private boolean reachedEof = false;
private ReadListener readListener;
public BoundedServletInputStream(ServletInputStream delegate, long limit) {
this.boundedIn = new BoundedInputStream(delegate, limit);
this.servletIn = delegate;
}
private void reachedEof() throws IOException {
reachedEof = true;
if (readListener != null) {
readListener.onAllDataRead();
}
}
/* BoundedInputStream */
@Override
public long skip(long n) throws IOException {
return boundedIn.skip(n);
}
@Override
public int available() throws IOException {
return boundedIn.available();
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int read = boundedIn.read(b, off, len);
if (read == -1) {
reachedEof();
}
return read;
}
@Override
public int read() throws IOException {
int aByte = boundedIn.read();
if (aByte == -1) {
reachedEof();
}
return aByte;
}
/* ServletInputStream */
@Override
public boolean isFinished() {
return reachedEof || servletIn.isFinished();
}
@Override
public boolean isReady() {
return !reachedEof && servletIn.isReady();
}
@Override
public void setReadListener(ReadListener readListener) {
servletIn.setReadListener(readListener);
this.readListener = readListener;
}
}
}
@@ -0,0 +1,171 @@
package org.cryptomator.webdav.filters;
import java.io.IOException;
import java.util.function.Function;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.cryptomator.webdav.filters.UriNormalizationFilter.ResourceTypeChecker.ResourceType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Normalizes all URIs contained in requests depending on the resource type of existing resources.
* URIs identifying directories will always end on "/", URIs identifying files will not.
*
* If the resource type is unknown, because the resource doesn't exist yet, this filter will determine the resource type based on the HTTP method,
* e.g. a MKCOL request will result in a directory..
*/
public class UriNormalizationFilter implements HttpFilter {
private static final Logger LOG = LoggerFactory.getLogger(UriNormalizationFilter.class);
private static final String[] FILE_METHODS = {"PUT"};
private static final String[] DIRECTORY_METHODS = {"MKCOL"};
private final ResourceTypeChecker resourceTypeChecker;
public UriNormalizationFilter(ResourceTypeChecker resourceTypeChecker) {
this.resourceTypeChecker = resourceTypeChecker;
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// no-op
}
@Override
public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
ResourceType resourceType = resourceTypeChecker.typeOfResource(request.getPathInfo());
HttpServletRequest normalizedRequest = resourceType.normalizedRequest(request);
chain.doFilter(normalizedRequest, response);
}
@Override
public void destroy() {
// no-op
}
private static HttpServletRequest normalizedFileRequest(HttpServletRequest originalRequest) {
LOG.debug("Treating resource as file: {}", originalRequest.getRequestURI());
return new FileUriRequest(originalRequest);
}
private static HttpServletRequest normalizedFolderRequest(HttpServletRequest originalRequest) {
LOG.debug("Treating resource as folder: {}", originalRequest.getRequestURI());
return new FolderUriRequest(originalRequest);
}
private static HttpServletRequest normalizedRequestForUnknownResource(HttpServletRequest originalRequest) {
final String requestMethod = originalRequest.getMethod().toUpperCase();
if (ArrayUtils.contains(FILE_METHODS, requestMethod)) {
return normalizedFileRequest(originalRequest);
} else if (ArrayUtils.contains(DIRECTORY_METHODS, requestMethod)) {
return normalizedFolderRequest(originalRequest);
} else {
LOG.debug("Could not determine resource type of resource: {}", originalRequest.getRequestURI());
return originalRequest;
}
}
@FunctionalInterface
public interface ResourceTypeChecker {
enum ResourceType {
FILE(UriNormalizationFilter::normalizedFileRequest), //
FOLDER(UriNormalizationFilter::normalizedFolderRequest), //
UNKNOWN(UriNormalizationFilter::normalizedRequestForUnknownResource);
private final Function<HttpServletRequest, HttpServletRequest> wrapper;
private ResourceType(Function<HttpServletRequest, HttpServletRequest> wrapper) {
this.wrapper = wrapper;
}
private HttpServletRequest normalizedRequest(HttpServletRequest request) {
return wrapper.apply(request);
}
};
/**
* Checks if the resource with the given resource name is a file, a folder or doesn't exist.
*
* @param resourcePath Relative URI of the resource in question.
* @return Type of the resource or {@link ResourceType#UNKNOWN UNKNOWN} for non-existing resources. Never <code>null</code>.
*/
ResourceType typeOfResource(String resourcePath);
}
/**
* Adjusts headers containing URIs depending on the request URI.
*/
private static class SuffixPreservingRequest extends HttpServletRequestWrapper {
private static final String HEADER_DESTINATION = "Destination";
private static final String METHOD_MOVE = "MOVE";
private static final String METHOD_COPY = "COPY";
public SuffixPreservingRequest(HttpServletRequest request) {
super(request);
}
@Override
public String getHeader(String name) {
if ((METHOD_MOVE.equalsIgnoreCase(getMethod()) || METHOD_COPY.equalsIgnoreCase(getMethod())) && HEADER_DESTINATION.equalsIgnoreCase(name)) {
return sameSuffixAsUri(super.getHeader(name));
} else {
return super.getHeader(name);
}
}
private String sameSuffixAsUri(String str) {
final String uri = this.getRequestURI();
if (uri.endsWith("/")) {
return StringUtils.appendIfMissing(str, "/");
} else {
return StringUtils.removeEnd(str, "/");
}
}
}
/**
* HTTP request, whose URI never ends on "/".
*/
private static class FileUriRequest extends SuffixPreservingRequest {
public FileUriRequest(HttpServletRequest request) {
super(request);
}
@Override
public String getRequestURI() {
return StringUtils.removeEnd(super.getRequestURI(), "/");
}
}
/**
* HTTP request, whose URI always ends on "/".
*/
private static class FolderUriRequest extends SuffixPreservingRequest {
public FolderUriRequest(HttpServletRequest request) {
super(request);
}
@Override
public String getRequestURI() {
return StringUtils.appendIfMissing(super.getRequestURI(), "/");
}
}
}
@@ -0,0 +1,121 @@
/*******************************************************************************
* Copyright (c) 2015 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.webdav.jackrabbitservlet;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;
import java.time.Instant;
import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.DavResource;
import org.apache.jackrabbit.webdav.DavResourceIterator;
import org.apache.jackrabbit.webdav.DavSession;
import org.apache.jackrabbit.webdav.io.InputContext;
import org.apache.jackrabbit.webdav.io.OutputContext;
import org.apache.jackrabbit.webdav.lock.LockManager;
import org.apache.jackrabbit.webdav.property.DavProperty;
import org.apache.jackrabbit.webdav.property.DavPropertyName;
import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
import org.cryptomator.filesystem.ReadableFile;
import org.cryptomator.filesystem.WritableFile;
import org.cryptomator.filesystem.jackrabbit.FileLocator;
class DavFile extends DavNode<FileLocator> {
private static final int BUFFER_SIZE = 32 * 1024;
public DavFile(FilesystemResourceFactory factory, LockManager lockManager, DavSession session, FileLocator node) {
super(factory, lockManager, session, node);
}
@Override
public boolean isCollection() {
return false;
}
@Override
public void spool(OutputContext outputContext) throws IOException {
outputContext.setModificationTime(node.lastModified().toEpochMilli());
if (!outputContext.hasStream()) {
return;
}
try (ReadableFile src = node.openReadable(); WritableByteChannel dst = Channels.newChannel(outputContext.getOutputStream())) {
outputContext.setContentLength(src.size());
ByteBuffer buf = ByteBuffer.allocate(BUFFER_SIZE);
do {
buf.clear();
src.read(buf);
buf.flip();
} while (dst.write(buf) > 0);
}
}
@Override
public void addMember(DavResource resource, InputContext inputContext) throws DavException {
throw new UnsupportedOperationException();
}
@Override
public DavResourceIterator getMembers() {
throw new UnsupportedOperationException();
}
@Override
public void removeMember(DavResource member) throws DavException {
throw new UnsupportedOperationException();
}
@Override
public void move(DavResource destination) throws DavException {
if (destination instanceof DavFile) {
DavFile dst = (DavFile) destination;
node.moveTo(dst.node);
} else {
throw new IllegalArgumentException("Destination not a DavFolder: " + destination.getClass().getName());
}
}
@Override
public void copy(DavResource destination, boolean shallow) throws DavException {
if (destination instanceof DavFile) {
DavFile dst = (DavFile) destination;
node.copyTo(dst.node);
} else {
throw new IllegalArgumentException("Destination not a DavFolder: " + destination.getClass().getName());
}
}
@Override
public DavProperty<?> getProperty(DavPropertyName name) {
if (DavPropertyName.GETCONTENTLENGTH.equals(name) && node.exists()) {
try (ReadableFile src = node.openReadable()) {
return new DefaultDavProperty<Long>(DavPropertyName.GETCONTENTLENGTH, src.size());
}
} else {
return super.getProperty(name);
}
}
@Override
protected void setModificationTime(Instant instant) {
try (WritableFile writable = node.openWritable()) {
writable.setLastModified(instant);
}
}
@Override
protected void setCreationTime(Instant instant) {
try (WritableFile writable = node.openWritable()) {
writable.setCreationTime(instant);
}
}
}
@@ -0,0 +1,161 @@
/*******************************************************************************
* Copyright (c) 2015 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.webdav.jackrabbitservlet;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.time.Instant;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.DavResource;
import org.apache.jackrabbit.webdav.DavResourceIterator;
import org.apache.jackrabbit.webdav.DavResourceIteratorImpl;
import org.apache.jackrabbit.webdav.DavServletResponse;
import org.apache.jackrabbit.webdav.DavSession;
import org.apache.jackrabbit.webdav.io.InputContext;
import org.apache.jackrabbit.webdav.io.OutputContext;
import org.apache.jackrabbit.webdav.lock.LockManager;
import org.apache.jackrabbit.webdav.property.DavPropertyName;
import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
import org.apache.jackrabbit.webdav.property.ResourceType;
import org.cryptomator.filesystem.File;
import org.cryptomator.filesystem.Folder;
import org.cryptomator.filesystem.Node;
import org.cryptomator.filesystem.WritableFile;
import org.cryptomator.filesystem.jackrabbit.FileLocator;
import org.cryptomator.filesystem.jackrabbit.FolderLocator;
class DavFolder extends DavNode<FolderLocator> {
private static final int BUFFER_SIZE = 32 * 1024;
public DavFolder(FilesystemResourceFactory factory, LockManager lockManager, DavSession session, FolderLocator folder) {
super(factory, lockManager, session, folder);
properties.add(new ResourceType(ResourceType.COLLECTION));
properties.add(new DefaultDavProperty<Integer>(DavPropertyName.ISCOLLECTION, 1));
}
@Override
public boolean isCollection() {
return true;
}
@Override
public void spool(OutputContext outputContext) throws IOException {
// no-op
}
@Override
public void addMember(DavResource resource, InputContext inputContext) throws DavException {
if (resource instanceof DavFolder) {
addMemberFolder((DavFolder) resource);
} else if (resource instanceof DavFile) {
addMemberFile((DavFile) resource, inputContext.getInputStream());
} else {
throw new IllegalArgumentException("Unsupported resource type: " + resource.getClass().getName());
}
}
private void addMemberFolder(DavFolder memberFolder) {
node.folder(memberFolder.getDisplayName()).create();
}
private void addMemberFile(DavFile memberFile, InputStream inputStream) {
try (ReadableByteChannel src = Channels.newChannel(inputStream); WritableFile dst = node.file(memberFile.getDisplayName()).openWritable()) {
ByteBuffer buf = ByteBuffer.allocate(BUFFER_SIZE);
while (src.read(buf) != -1) {
buf.flip();
dst.write(buf);
buf.clear();
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
@Override
public DavResourceIterator getMembers() {
final Stream<DavFolder> folders = node.folders().map(this::folderToDavFolder);
final Stream<DavFile> files = node.files().map(this::fileToDavFile);
return new DavResourceIteratorImpl(Stream.concat(folders, files).collect(Collectors.toList()));
}
private DavFolder folderToDavFolder(FolderLocator memberFolder) {
return factory.createFolder(memberFolder, session);
}
private DavFile fileToDavFile(FileLocator memberFile) {
return factory.createFile(memberFile, session);
}
@Override
public void removeMember(DavResource member) throws DavException {
final Node child = getMemberNode(member.getDisplayName());
if (child instanceof Folder) {
Folder folder = (Folder) child;
folder.delete();
} else if (child instanceof File) {
File file = (File) child;
try (WritableFile writable = file.openWritable()) {
writable.delete();
}
} else {
throw new IllegalStateException("Unexpected node type: " + child.getClass().getName());
}
}
/**
* @throws DavException
* Error 404 if no child with the given name exists
*/
private Node getMemberNode(String name) throws DavException {
return node.children().filter(c -> c.name().equals(name)).findAny().orElseThrow(() -> {
return new DavException(DavServletResponse.SC_NOT_FOUND, "No such file or directory: " + node.getResourcePath() + name);
});
}
@Override
public void move(DavResource destination) throws DavException {
if (destination instanceof DavFolder) {
DavFolder dst = (DavFolder) destination;
node.moveTo(dst.node);
} else {
throw new IllegalArgumentException("Destination not a DavFolder: " + destination.getClass().getName());
}
}
@Override
public void copy(DavResource destination, boolean shallow) throws DavException {
if (shallow) {
throw new UnsupportedOperationException("Shallow copy of directories not supported.");
} else if (destination instanceof DavFolder) {
DavFolder dst = (DavFolder) destination;
node.copyTo(dst.node);
} else {
throw new IllegalArgumentException("Destination not a DavFolder: " + destination.getClass().getName());
}
}
@Override
protected void setModificationTime(Instant instant) {
// TODO Auto-generated method stub
}
@Override
protected void setCreationTime(Instant instant) {
node.setCreationTime(instant);
}
}
@@ -0,0 +1,258 @@
/*******************************************************************************
* Copyright (c) 2015 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.webdav.jackrabbitservlet;
import java.io.UncheckedIOException;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.temporal.Temporal;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.DavResource;
import org.apache.jackrabbit.webdav.DavResourceLocator;
import org.apache.jackrabbit.webdav.DavSession;
import org.apache.jackrabbit.webdav.MultiStatusResponse;
import org.apache.jackrabbit.webdav.lock.ActiveLock;
import org.apache.jackrabbit.webdav.lock.LockInfo;
import org.apache.jackrabbit.webdav.lock.LockManager;
import org.apache.jackrabbit.webdav.lock.Scope;
import org.apache.jackrabbit.webdav.lock.Type;
import org.apache.jackrabbit.webdav.property.DavProperty;
import org.apache.jackrabbit.webdav.property.DavPropertyName;
import org.apache.jackrabbit.webdav.property.DavPropertyNameSet;
import org.apache.jackrabbit.webdav.property.DavPropertySet;
import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
import org.apache.jackrabbit.webdav.property.PropEntry;
import org.cryptomator.filesystem.jackrabbit.FileSystemResourceLocator;
abstract class DavNode<T extends FileSystemResourceLocator> implements DavResource {
private static final String DAV_COMPLIANCE_CLASSES = "1, 2";
private static final String[] DAV_CREATIONDATE_PROPNAMES = {DavPropertyName.CREATIONDATE.getName(), "Win32CreationTime"};
private static final String[] DAV_MODIFIEDDATE_PROPNAMES = {DavPropertyName.GETLASTMODIFIED.getName(), "Win32LastModifiedTime"};
protected final FilesystemResourceFactory factory;
protected final LockManager lockManager;
protected final DavSession session;
protected final T node;
protected final DavPropertySet properties;
public DavNode(FilesystemResourceFactory factory, LockManager lockManager, DavSession session, T node) {
this.factory = factory;
this.lockManager = lockManager;
this.session = session;
this.node = node;
this.properties = new DavPropertySet();
}
@Override
public String getComplianceClass() {
return DAV_COMPLIANCE_CLASSES;
}
@Override
public String getSupportedMethods() {
return METHODS;
}
@Override
public boolean exists() {
return node.exists();
}
@Override
public String getDisplayName() {
return node.name();
}
@Override
public DavResourceLocator getLocator() {
return node;
}
@Override
public String getResourcePath() {
return node.getResourcePath();
}
@Override
public String getHref() {
return node.getHref();
}
@Override
public long getModificationTime() {
try {
return node.lastModified().toEpochMilli();
} catch (UncheckedIOException e) {
return -1l;
}
}
protected abstract void setModificationTime(Instant instant);
protected abstract void setCreationTime(Instant instant);
@Override
public DavPropertyName[] getPropertyNames() {
return getProperties().getPropertyNames();
}
@Override
public DavProperty<?> getProperty(DavPropertyName name) {
final String namespacelessPropertyName = name.getName();
if (Arrays.asList(DAV_CREATIONDATE_PROPNAMES).contains(namespacelessPropertyName)) {
return creationDateProperty(name).orElse(null);
} else if (Arrays.asList(DAV_MODIFIEDDATE_PROPNAMES).contains(namespacelessPropertyName)) {
Temporal lastModifiedDate = OffsetDateTime.ofInstant(node.lastModified(), ZoneOffset.UTC);
return new DefaultDavProperty<>(name, DateTimeFormatter.RFC_1123_DATE_TIME.format(lastModifiedDate));
} else {
return properties.get(name);
}
}
/**
* Returns a current snapshot of all available properties.
*/
@Override
public DavPropertySet getProperties() {
// creation date:
if (node.exists()) {
creationDateProperty(DavPropertyName.CREATIONDATE).ifPresent(properties::add);
}
// modification date:
if (node.exists()) {
Temporal lastModifiedDate = OffsetDateTime.ofInstant(node.lastModified(), ZoneOffset.UTC);
String lastModifiedDateStr = DateTimeFormatter.RFC_1123_DATE_TIME.format(lastModifiedDate);
System.err.println(lastModifiedDateStr);
DavProperty<String> lastModifiedDateProp = new DefaultDavProperty<>(DavPropertyName.GETLASTMODIFIED, lastModifiedDateStr);
properties.add(lastModifiedDateProp);
}
return properties;
}
private Optional<DavProperty<?>> creationDateProperty(DavPropertyName name) {
return node.creationTime() //
.map(creationTime -> OffsetDateTime.ofInstant(creationTime, ZoneOffset.UTC)) //
.map(creationDate -> new DefaultDavProperty<>(name, DateTimeFormatter.RFC_1123_DATE_TIME.format(creationDate)));
}
@Override
public void setProperty(DavProperty<?> property) throws DavException {
final String namespacelessPropertyName = property.getName().getName();
if (Arrays.asList(DAV_CREATIONDATE_PROPNAMES).contains(namespacelessPropertyName) && property.getValue() instanceof String) {
String createDateStr = (String) property.getValue();
OffsetDateTime creationDate = OffsetDateTime.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(createDateStr));
this.setCreationTime(creationDate.toInstant());
} else if (Arrays.asList(DAV_MODIFIEDDATE_PROPNAMES).contains(namespacelessPropertyName) && property.getValue() instanceof String) {
String lastModifiedDateStr = (String) property.getValue();
OffsetDateTime lastModifiedDate = OffsetDateTime.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(lastModifiedDateStr));
this.setModificationTime(lastModifiedDate.toInstant());
}
properties.add(property);
}
@Override
public void removeProperty(DavPropertyName propertyName) throws DavException {
getProperties().remove(propertyName);
}
@Override
public MultiStatusResponse alterProperties(List<? extends PropEntry> changeList) throws DavException {
final DavPropertyNameSet names = new DavPropertyNameSet();
for (final PropEntry entry : changeList) {
if (entry instanceof DavProperty) {
final DavProperty<?> prop = (DavProperty<?>) entry;
this.setProperty(prop);
names.add(prop.getName());
} else if (entry instanceof DavPropertyName) {
final DavPropertyName name = (DavPropertyName) entry;
this.removeProperty(name);
names.add(name);
}
}
return new MultiStatusResponse(this, names);
}
@Override
public DavResource getCollection() {
if (node.isRootLocation()) {
return null;
}
assert node.parent().isPresent() : "as my mom always sais: if it's not root, it has a parent";
final FileSystemResourceLocator parentPath = node.parent().get();
try {
return factory.createResource(parentPath, session);
} catch (DavException e) {
throw new IllegalStateException("Unable to get parent resource with path " + parentPath, e);
}
}
@Override
public boolean isLockable(Type type, Scope scope) {
return true;
}
@Override
public boolean hasLock(Type type, Scope scope) {
return getLock(type, scope) != null;
}
@Override
public ActiveLock getLock(Type type, Scope scope) {
return lockManager.getLock(type, scope, this);
}
@Override
public ActiveLock[] getLocks() {
final ActiveLock exclusiveWriteLock = getLock(Type.WRITE, Scope.EXCLUSIVE);
if (exclusiveWriteLock != null) {
return new ActiveLock[] {exclusiveWriteLock};
} else {
return new ActiveLock[0];
}
}
@Override
public ActiveLock lock(LockInfo reqLockInfo) throws DavException {
return lockManager.createLock(reqLockInfo, this);
}
@Override
public ActiveLock refreshLock(LockInfo reqLockInfo, String lockToken) throws DavException {
return lockManager.refreshLock(reqLockInfo, lockToken, this);
}
@Override
public void unlock(String lockToken) throws DavException {
lockManager.releaseLock(lockToken, this);
}
@Override
public void addLockManager(LockManager lockmgr) {
throw new UnsupportedOperationException("Locks are managed");
}
@Override
public FilesystemResourceFactory getFactory() {
return factory;
}
@Override
public DavSession getSession() {
return session;
}
}
@@ -0,0 +1,45 @@
/*******************************************************************************
* Copyright (c) 2014, 2015 Sebastian Stenzel
* 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.webdav.jackrabbitservlet;
import java.util.HashSet;
import org.apache.jackrabbit.webdav.DavSession;
class DavSessionImpl implements DavSession {
private final HashSet<String> lockTokens = new HashSet<String>();
private final HashSet<Object> references = new HashSet<Object>();
@Override
public void addReference(Object reference) {
references.add(reference);
}
@Override
public void removeReference(Object reference) {
references.remove(reference);
}
@Override
public void addLockToken(String token) {
lockTokens.add(token);
}
@Override
public String[] getLockTokens() {
return lockTokens.toArray(new String[lockTokens.size()]);
}
@Override
public void removeLockToken(String token) {
lockTokens.remove(token);
}
}
@@ -0,0 +1,36 @@
/*******************************************************************************
* Copyright (c) 2014, 2015 Sebastian Stenzel
* 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.webdav.jackrabbitservlet;
import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.DavSession;
import org.apache.jackrabbit.webdav.DavSessionProvider;
import org.apache.jackrabbit.webdav.WebdavRequest;
class DavSessionProviderImpl implements DavSessionProvider {
@Override
public boolean attachSession(WebdavRequest request) throws DavException {
// every request gets a new session
final DavSession session = new DavSessionImpl();
session.addReference(request);
request.setDavSession(session);
return true;
}
@Override
public void releaseSession(WebdavRequest request) {
final DavSession session = request.getDavSession();
if (session != null) {
session.removeReference(request);
request.setDavSession(null);
}
}
}
@@ -0,0 +1,57 @@
/*******************************************************************************
* Copyright (c) 2015 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.webdav.jackrabbitservlet;
import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.DavResource;
import org.apache.jackrabbit.webdav.DavResourceFactory;
import org.apache.jackrabbit.webdav.DavResourceLocator;
import org.apache.jackrabbit.webdav.DavServletRequest;
import org.apache.jackrabbit.webdav.DavServletResponse;
import org.apache.jackrabbit.webdav.DavSession;
import org.apache.jackrabbit.webdav.lock.LockManager;
import org.apache.jackrabbit.webdav.lock.SimpleLockManager;
import org.cryptomator.filesystem.jackrabbit.FileLocator;
import org.cryptomator.filesystem.jackrabbit.FolderLocator;
class FilesystemResourceFactory implements DavResourceFactory {
private final LockManager lockManager;
public FilesystemResourceFactory() {
this.lockManager = new SimpleLockManager();
}
@Override
public DavResource createResource(DavResourceLocator locator, DavServletRequest request, DavServletResponse response) throws DavException {
return createResource(locator, request.getDavSession());
}
@Override
public DavResource createResource(DavResourceLocator locator, DavSession session) throws DavException {
if (locator instanceof FolderLocator) {
FolderLocator folder = (FolderLocator) locator;
return createFolder(folder, session);
} else if (locator instanceof FileLocator) {
FileLocator file = (FileLocator) locator;
return createFile(file, session);
} else {
throw new IllegalArgumentException("Unsupported locator type " + locator.getClass().getName());
}
}
DavFolder createFolder(FolderLocator folder, DavSession session) {
return new DavFolder(this, lockManager, session, folder);
}
DavFile createFile(FileLocator file, DavSession session) {
return new DavFile(this, lockManager, session, file);
}
}
@@ -0,0 +1,71 @@
/*******************************************************************************
* Copyright (c) 2015 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.webdav.jackrabbitservlet;
import java.net.URI;
import org.apache.jackrabbit.webdav.DavLocatorFactory;
import org.apache.jackrabbit.webdav.DavResource;
import org.apache.jackrabbit.webdav.DavResourceFactory;
import org.apache.jackrabbit.webdav.DavSessionProvider;
import org.apache.jackrabbit.webdav.WebdavRequest;
import org.apache.jackrabbit.webdav.server.AbstractWebdavServlet;
import org.cryptomator.filesystem.Folder;
import org.cryptomator.filesystem.jackrabbit.FileSystemResourceLocatorFactory;
class WebDavServlet extends AbstractWebdavServlet {
private static final long serialVersionUID = -6632687979352625020L;
private final DavSessionProvider davSessionProvider;
private final DavLocatorFactory davLocatorFactory;
private final DavResourceFactory davResourceFactory;
public WebDavServlet(URI contextRootUri, Folder root) {
davSessionProvider = new DavSessionProviderImpl();
davLocatorFactory = new FileSystemResourceLocatorFactory(contextRootUri, root);
davResourceFactory = new FilesystemResourceFactory();
}
@Override
protected boolean isPreconditionValid(WebdavRequest request, DavResource resource) {
return !resource.exists() || request.matchesIfHeader(resource);
}
@Override
public DavSessionProvider getDavSessionProvider() {
return davSessionProvider;
}
@Override
public void setDavSessionProvider(DavSessionProvider davSessionProvider) {
throw new UnsupportedOperationException("Setting davSessionProvider not supported.");
}
@Override
public DavLocatorFactory getLocatorFactory() {
return davLocatorFactory;
}
@Override
public void setLocatorFactory(DavLocatorFactory locatorFactory) {
throw new UnsupportedOperationException("Setting locatorFactory not supported.");
}
@Override
public DavResourceFactory getResourceFactory() {
return davResourceFactory;
}
@Override
public void setResourceFactory(DavResourceFactory resourceFactory) {
throw new UnsupportedOperationException("Setting resourceFactory not supported.");
}
}
@@ -0,0 +1,66 @@
package org.cryptomator.webdav.jackrabbitservlet;
import java.net.URI;
import java.util.EnumSet;
import javax.servlet.DispatcherType;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.filesystem.Folder;
import org.cryptomator.webdav.filters.AcceptRangeFilter;
import org.cryptomator.webdav.filters.LoggingHttpFilter;
import org.cryptomator.webdav.filters.MacChunkedPutCompatibilityFilter;
import org.cryptomator.webdav.filters.UriNormalizationFilter;
import org.cryptomator.webdav.filters.UriNormalizationFilter.ResourceTypeChecker;
import org.cryptomator.webdav.filters.UriNormalizationFilter.ResourceTypeChecker.ResourceType;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class WebDavServletContextFactory {
private static final Logger LOG = LoggerFactory.getLogger(WebDavServletContextFactory.class);
private static final String WILDCARD = "/*";
/**
* Creates a new Jetty ServletContextHandler, that can be be added to a servletCollection as follows:
*
* <pre>
* ServletContextHandler context = factory.create(...);
* servletCollection.addHandler(context);
* servletCollection.mapContexts();
* </pre>
*
* @param contextRoot The URI of the context root. Its path will be used as the servlet's context path.
* @param root The location within a filesystem that shall be served via WebDAV.
* @return A new Jetty servlet context handler.
*/
public ServletContextHandler create(URI contextRoot, Folder root) {
final ResourceTypeChecker resourceTypeChecker = (path) -> {
if (root.resolveFolder(path).exists()) {
return ResourceType.FOLDER;
} else if (root.resolveFile(path).exists()) {
return ResourceType.FILE;
} else {
return ResourceType.UNKNOWN;
}
};
final String contextPath = StringUtils.removeEnd(contextRoot.getPath(), "/");
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(AcceptRangeFilter.class, WILDCARD, EnumSet.of(DispatcherType.REQUEST));
servletContext.addFilter(new FilterHolder(new UriNormalizationFilter(resourceTypeChecker)), WILDCARD, EnumSet.of(DispatcherType.REQUEST));
if (SystemUtils.IS_OS_MAC_OSX) {
servletContext.addFilter(MacChunkedPutCompatibilityFilter.class, WILDCARD, EnumSet.of(DispatcherType.REQUEST));
}
if (LOG.isDebugEnabled()) {
servletContext.addFilter(LoggingHttpFilter.class, WILDCARD, EnumSet.of(DispatcherType.REQUEST));
}
return servletContext;
}
}
@@ -0,0 +1,61 @@
package org.cryptomator.filesystem.jackrabbit;
import java.net.URI;
import java.nio.ByteBuffer;
import org.cryptomator.filesystem.FileSystem;
import org.cryptomator.filesystem.WritableFile;
import org.cryptomator.filesystem.inmem.InMemoryFileSystem;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class FileSystemResourceLocatorFactoryTest {
private FileSystemResourceLocatorFactory factory;
@Before
public void setupLocatorFactory() {
final FileSystem fs = new InMemoryFileSystem();
fs.folder("existingFolder").create();
try (WritableFile writable = fs.file("existingFile").openWritable()) {
writable.write(ByteBuffer.allocate(0));
}
factory = new FileSystemResourceLocatorFactory(URI.create("http://localhost/contextroot"), fs);
}
@Test
public void testCreateFolders() {
FileSystemResourceLocator locator = factory.createResourceLocator(null, null, "/foo/bar/");
Assert.assertTrue(locator instanceof FolderLocator);
Assert.assertEquals("bar", locator.name());
Assert.assertEquals("foo", locator.parent().get().name());
Assert.assertEquals("", locator.parent().get().parent().get().name());
Assert.assertFalse(locator.parent().get().parent().get().parent().isPresent());
locator = factory.createResourceLocator("http://localhost/contextroot", "http://localhost/contextroot/foo/bar/");
Assert.assertTrue(locator instanceof FolderLocator);
Assert.assertEquals("bar", locator.name());
Assert.assertEquals("foo", locator.parent().get().name());
Assert.assertEquals("", locator.parent().get().parent().get().name());
Assert.assertFalse(locator.parent().get().parent().get().parent().isPresent());
}
@Test
public void testCreateFiles() {
FileSystemResourceLocator locator = factory.createResourceLocator(null, null, "/foo/bar");
Assert.assertTrue(locator instanceof FileLocator);
Assert.assertEquals("bar", locator.name());
Assert.assertEquals("foo", locator.parent().get().name());
Assert.assertEquals("", locator.parent().get().parent().get().name());
Assert.assertFalse(locator.parent().get().parent().get().parent().isPresent());
locator = factory.createResourceLocator("http://localhost/contextroot", "http://localhost/contextroot/foo/bar");
Assert.assertTrue(locator instanceof FileLocator);
Assert.assertEquals("bar", locator.name());
Assert.assertEquals("foo", locator.parent().get().name());
Assert.assertEquals("", locator.parent().get().parent().get().name());
Assert.assertFalse(locator.parent().get().parent().get().parent().isPresent());
}
}
@@ -0,0 +1,174 @@
package org.cryptomator.webdav.filters;
import static java.lang.String.format;
import static java.util.Arrays.asList;
import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LoggingHttpFilter implements HttpFilter {
private static final Set<String> METHODS_TO_LOG_DETAILED = methodsToLog();
private static final Set<String> methodsToLog() {
String methodsToLog = System.getProperty("cryptomator.LoggingHttpFilter.methodsToLogDetailed");
if (methodsToLog == null) {
return Collections.emptySet();
} else {
return new HashSet<>(asList(methodsToLog.toUpperCase().split(",")));
}
}
private final Logger LOG = LoggerFactory.getLogger(LoggingHttpFilter.class);
@Override
public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
if (METHODS_TO_LOG_DETAILED.contains(request.getMethod().toUpperCase())) {
logDetailed(request, response, chain);
} else {
logBasic(request, response, chain);
}
}
private void logBasic(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
Optional<Throwable> thrown = Optional.empty();
try {
chain.doFilter(request, response);
} catch (IOException | ServletException e) {
thrown = Optional.of(e);
throw e;
} catch (RuntimeException | Error e) {
thrown = Optional.of(e);
throw e;
} finally {
if (thrown.isPresent()) {
logError(request, thrown.get());
} else {
logSuccess(request, response);
}
}
}
private void logDetailed(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
RecordingHttpServletRequest recordingRequest = new RecordingHttpServletRequest(request);
RecordingHttpServletResponse recordingResponse = new RecordingHttpServletResponse(response);
Optional<Throwable> thrown = Optional.empty();
try {
chain.doFilter(recordingRequest, recordingResponse);
} catch (IOException | ServletException e) {
thrown = Optional.of(e);
throw e;
} catch (RuntimeException | Error e) {
thrown = Optional.of(e);
throw e;
} finally {
if (thrown.isPresent()) {
logError(recordingRequest, thrown.get());
} else {
logSuccess(recordingRequest, recordingResponse);
}
}
}
private void logSuccess(HttpServletRequest request, HttpServletResponse response) {
LOG.debug(format(
"## Request ##\n" + //
"%s %s %s\n" //
+ "%s\n" //
+ "## Response ##\n" //
+ "%s %s\n" //
+ "%s\n", //
request.getMethod(), request.getRequestURI(), request.getProtocol(), //
headers(request), //
request.getProtocol(), response.getStatus(), //
headers(response)));
}
private void logError(HttpServletRequest request, Throwable throwable) {
LOG.error(
format("## Request ##\n" + //
"%s %s %s\n" //
+ "%s\n" //
+ "%s\n\n", //
request.getMethod(), request.getRequestURI(), request.getProtocol(), //
headers(request)), //
throwable);
}
private void logSuccess(RecordingHttpServletRequest request, RecordingHttpServletResponse response) {
LOG.debug(format(
"## Request ##\n" + //
"%s %s %s\n" //
+ "%s\n" //
+ "%s\n\n" //
+ "## Response ##\n" //
+ "%s %s\n" //
+ "%s\n" //
+ "%s", //
request.getMethod(), request.getRequestURI(), request.getProtocol(), //
headers(request), //
new String(request.getRecording()), //
request.getProtocol(), response.getStatus(), //
headers(response), //
new String(response.getRecording())));
}
private void logError(RecordingHttpServletRequest request, Throwable throwable) {
LOG.error(
format("## Request ##\n" + //
"%s %s %s\n" //
+ "%s\n" //
+ "%s\n\n", //
request.getMethod(), request.getRequestURI(), request.getProtocol(), //
headers(request), //
new String(request.getRecording())), //
throwable);
}
private String headers(HttpServletResponse response) {
StringBuilder result = new StringBuilder();
for (String headerName : response.getHeaderNames()) {
for (String value : response.getHeaders(headerName)) {
result.append(headerName).append(": ").append(value).append('\n');
}
}
return result.toString();
}
private String headers(HttpServletRequest request) {
StringBuilder result = new StringBuilder();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
Enumeration<String> values = request.getHeaders(headerName);
while (values.hasMoreElements()) {
result.append(headerName).append(": ").append(values.nextElement()).append('\n');
}
}
return result.toString();
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// empty
}
@Override
public void destroy() {
// empty
}
}
@@ -0,0 +1,122 @@
package org.cryptomator.webdav.filters;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
public class MacChunkedPutCompatibilityFilterTest {
private MacChunkedPutCompatibilityFilter filter;
private FilterChain chain;
private HttpServletRequest request;
private HttpServletResponse response;
@Before
public void setup() {
filter = new MacChunkedPutCompatibilityFilter();
chain = Mockito.mock(FilterChain.class);
request = Mockito.mock(HttpServletRequest.class);
response = Mockito.mock(HttpServletResponse.class);
}
@Test
public void testUnfilteredGetRequest() throws IOException, ServletException {
Mockito.when(request.getMethod()).thenReturn("GET");
filter.doFilter(request, response, chain);
ArgumentCaptor<HttpServletRequest> wrappedReq = ArgumentCaptor.forClass(HttpServletRequest.class);
Mockito.verify(chain).doFilter(wrappedReq.capture(), Mockito.any(ServletResponse.class));
Assert.assertSame(request, wrappedReq.getValue());
}
@Test
public void testUnfilteredPutRequest1() throws IOException, ServletException {
Mockito.when(request.getMethod()).thenReturn("PUT");
Mockito.when(request.getHeader("Transfer-Encoding")).thenReturn(null);
filter.doFilter(request, response, chain);
ArgumentCaptor<HttpServletRequest> wrappedReq = ArgumentCaptor.forClass(HttpServletRequest.class);
Mockito.verify(chain).doFilter(wrappedReq.capture(), Mockito.any(ServletResponse.class));
Assert.assertSame(request, wrappedReq.getValue());
}
@Test
public void testUnfilteredPutRequest2() throws IOException, ServletException {
Mockito.when(request.getMethod()).thenReturn("PUT");
Mockito.when(request.getHeader("Transfer-Encoding")).thenReturn("chunked");
Mockito.when(request.getHeader("X-Expected-Entity-Length")).thenReturn(null);
filter.doFilter(request, response, chain);
ArgumentCaptor<HttpServletRequest> wrappedReq = ArgumentCaptor.forClass(HttpServletRequest.class);
Mockito.verify(chain).doFilter(wrappedReq.capture(), Mockito.any(ServletResponse.class));
Assert.assertSame(request, wrappedReq.getValue());
}
@Test
public void testMalformedXExpectedEntityLengthHeader() throws IOException, ServletException {
Mockito.when(request.getMethod()).thenReturn("PUT");
Mockito.when(request.getHeader("Transfer-Encoding")).thenReturn("chunked");
Mockito.when(request.getHeader("X-Expected-Entity-Length")).thenReturn("NaN");
filter.doFilter(request, response, chain);
Mockito.verify(response).sendError(Mockito.eq(HttpServletResponse.SC_BAD_REQUEST), Mockito.anyString());
Mockito.verifyNoMoreInteractions(chain);
}
/* actual input stream testing */
@Test
public void testBoundedInputStream() throws IOException, ServletException {
ServletInputStream in = Mockito.mock(ServletInputStream.class);
Mockito.when(request.getMethod()).thenReturn("PUT");
Mockito.when(request.getHeader("Transfer-Encoding")).thenReturn("chunked");
Mockito.when(request.getHeader("X-Expected-Entity-Length")).thenReturn("5");
Mockito.when(request.getInputStream()).thenReturn(in);
filter.doFilter(request, response, chain);
ArgumentCaptor<HttpServletRequest> wrappedReq = ArgumentCaptor.forClass(HttpServletRequest.class);
Mockito.verify(chain).doFilter(wrappedReq.capture(), Mockito.any(ServletResponse.class));
ServletInputStream wrappedIn = wrappedReq.getValue().getInputStream();
Mockito.when(in.isFinished()).thenReturn(false);
Assert.assertFalse(wrappedIn.isFinished());
Mockito.when(in.isReady()).thenReturn(true);
Assert.assertTrue(wrappedIn.isReady());
Mockito.when(in.read()).thenReturn(0xFF);
Assert.assertEquals(0xFF, wrappedIn.read());
Mockito.when(in.available()).thenReturn(100);
Assert.assertEquals(100, wrappedIn.available());
Mockito.when(in.skip(2)).thenReturn(2l);
Assert.assertEquals(2, wrappedIn.skip(2));
Mockito.when(in.read(Mockito.any(), Mockito.eq(0), Mockito.eq(100))).thenReturn(100);
Mockito.when(in.read(Mockito.any(), Mockito.eq(0), Mockito.eq(2))).thenReturn(2);
Assert.assertEquals(2, wrappedIn.read(new byte[100], 0, 100));
Mockito.when(in.read()).thenReturn(0xFF);
Assert.assertEquals(-1, wrappedIn.read());
Mockito.when(in.isFinished()).thenReturn(false);
Assert.assertTrue(wrappedIn.isFinished());
Mockito.when(in.isReady()).thenReturn(true);
Assert.assertFalse(wrappedIn.isReady());
}
}
@@ -0,0 +1,27 @@
package org.cryptomator.webdav.filters;
import java.io.IOException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
class RecordingHttpServletRequest extends HttpServletRequestWrapper {
private final RecordingServletInputStream recording;
public RecordingHttpServletRequest(HttpServletRequest request) throws IOException {
super(request);
recording = new RecordingServletInputStream(request.getInputStream());
}
@Override
public ServletInputStream getInputStream() throws IOException {
return recording;
}
public byte[] getRecording() {
return recording.getRecording();
}
}
@@ -0,0 +1,27 @@
package org.cryptomator.webdav.filters;
import java.io.IOException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
class RecordingHttpServletResponse extends HttpServletResponseWrapper {
private final RecordingServletOutputStream recording;
public RecordingHttpServletResponse(HttpServletResponse response) throws IOException {
super(response);
recording = new RecordingServletOutputStream(response.getOutputStream());
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return recording;
}
public byte[] getRecording() {
return recording.getRecording();
}
}
@@ -0,0 +1,73 @@
package org.cryptomator.webdav.filters;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import org.apache.commons.io.input.TeeInputStream;
class RecordingServletInputStream extends ServletInputStream {
private final ServletInputStream delegate;
private final TeeInputStream teeInputStream;
private final ByteArrayOutputStream recording = new ByteArrayOutputStream(4096);
public RecordingServletInputStream(ServletInputStream delegate) {
this.delegate = delegate;
this.teeInputStream = new TeeInputStream(delegate, recording);
}
public int read() throws IOException {
return teeInputStream.read();
}
public int read(byte[] b) throws IOException {
return teeInputStream.read(b);
}
public int read(byte[] b, int off, int len) throws IOException {
return teeInputStream.read(b, off, len);
}
public boolean isFinished() {
return delegate.isFinished();
}
public boolean isReady() {
return delegate.isReady();
}
public void setReadListener(ReadListener readListener) {
delegate.setReadListener(readListener);
}
public long skip(long n) throws IOException {
return teeInputStream.skip(n);
}
public int available() throws IOException {
return teeInputStream.available();
}
public void close() throws IOException {
teeInputStream.close();
}
public byte[] getRecording() {
return recording.toByteArray();
}
public void mark(int readlimit) {
}
public void reset() throws IOException {
throw new IOException("Mark not supported");
}
public boolean markSupported() {
return false;
}
}
@@ -0,0 +1,54 @@
package org.cryptomator.webdav.filters;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import org.apache.commons.io.output.TeeOutputStream;
class RecordingServletOutputStream extends ServletOutputStream {
private final ServletOutputStream delegate;
private final TeeOutputStream teeOutputStream;
private final ByteArrayOutputStream recording = new ByteArrayOutputStream(4096);
public RecordingServletOutputStream(ServletOutputStream delegate) {
this.delegate = delegate;
this.teeOutputStream = new TeeOutputStream(delegate, recording);
}
public void write(int b) throws IOException {
teeOutputStream.write(b);
}
public void write(byte[] b) throws IOException {
teeOutputStream.write(b);
}
public void write(byte[] b, int off, int len) throws IOException {
teeOutputStream.write(b, off, len);
}
public void flush() throws IOException {
teeOutputStream.flush();
}
public void close() throws IOException {
teeOutputStream.close();
}
public boolean isReady() {
return delegate.isReady();
}
public void setWriteListener(WriteListener writeListener) {
delegate.setWriteListener(writeListener);
}
public byte[] getRecording() {
return recording.toByteArray();
}
}
@@ -0,0 +1,162 @@
package org.cryptomator.webdav.filters;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.cryptomator.webdav.filters.UriNormalizationFilter.ResourceTypeChecker;
import org.cryptomator.webdav.filters.UriNormalizationFilter.ResourceTypeChecker.ResourceType;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
public class UriNormalizationFilterTest {
private ResourceTypeChecker resourceTypeChecker;
private UriNormalizationFilter filter;
private FilterChain chain;
private HttpServletRequest request;
private HttpServletResponse response;
@Before
public void setup() {
resourceTypeChecker = Mockito.mock(ResourceTypeChecker.class);
filter = new UriNormalizationFilter(resourceTypeChecker);
chain = Mockito.mock(FilterChain.class);
request = Mockito.mock(HttpServletRequest.class);
response = Mockito.mock(HttpServletResponse.class);
Mockito.when(resourceTypeChecker.typeOfResource("/file")).thenReturn(ResourceType.FILE);
Mockito.when(resourceTypeChecker.typeOfResource("/file/")).thenReturn(ResourceType.FILE);
Mockito.when(resourceTypeChecker.typeOfResource("/folder")).thenReturn(ResourceType.FOLDER);
Mockito.when(resourceTypeChecker.typeOfResource("/folder/")).thenReturn(ResourceType.FOLDER);
Mockito.when(resourceTypeChecker.typeOfResource("/404")).thenReturn(ResourceType.UNKNOWN);
Mockito.when(resourceTypeChecker.typeOfResource("/404/")).thenReturn(ResourceType.UNKNOWN);
}
/* FILE */
@Test
public void testFileRequest1() throws IOException, ServletException {
Mockito.when(request.getPathInfo()).thenReturn("/file");
Mockito.when(request.getRequestURI()).thenReturn("/file");
filter.doFilter(request, response, chain);
Mockito.verify(resourceTypeChecker).typeOfResource("/file");
ArgumentCaptor<HttpServletRequest> wrappedReq = ArgumentCaptor.forClass(HttpServletRequest.class);
Mockito.verify(chain).doFilter(wrappedReq.capture(), Mockito.any(ServletResponse.class));
Assert.assertEquals("/file", wrappedReq.getValue().getRequestURI());
}
@Test
public void testFileRequest2() throws IOException, ServletException {
Mockito.when(request.getPathInfo()).thenReturn("/file/");
Mockito.when(request.getRequestURI()).thenReturn("/file/");
filter.doFilter(request, response, chain);
Mockito.verify(resourceTypeChecker).typeOfResource("/file/");
ArgumentCaptor<HttpServletRequest> wrappedReq = ArgumentCaptor.forClass(HttpServletRequest.class);
Mockito.verify(chain).doFilter(wrappedReq.capture(), Mockito.any(ServletResponse.class));
Assert.assertEquals("/file", wrappedReq.getValue().getRequestURI());
}
@Test
public void testCopyFileRequest() throws IOException, ServletException {
Mockito.when(request.getPathInfo()).thenReturn("/file/");
Mockito.when(request.getRequestURI()).thenReturn("/file/");
Mockito.when(request.getMethod()).thenReturn("COPY");
Mockito.when(request.getHeader("Destination")).thenReturn("/404/");
filter.doFilter(request, response, chain);
ArgumentCaptor<HttpServletRequest> wrappedReq = ArgumentCaptor.forClass(HttpServletRequest.class);
Mockito.verify(chain).doFilter(wrappedReq.capture(), Mockito.any(ServletResponse.class));
Assert.assertEquals("/404", wrappedReq.getValue().getHeader("Destination"));
}
/* FOLDER */
@Test
public void testFolderRequest1() throws IOException, ServletException {
Mockito.when(request.getPathInfo()).thenReturn("/folder");
Mockito.when(request.getRequestURI()).thenReturn("/folder");
filter.doFilter(request, response, chain);
Mockito.verify(resourceTypeChecker).typeOfResource("/folder");
ArgumentCaptor<HttpServletRequest> wrappedReq = ArgumentCaptor.forClass(HttpServletRequest.class);
Mockito.verify(chain).doFilter(wrappedReq.capture(), Mockito.any(ServletResponse.class));
Assert.assertEquals("/folder/", wrappedReq.getValue().getRequestURI());
}
@Test
public void testFolderRequest2() throws IOException, ServletException {
Mockito.when(request.getPathInfo()).thenReturn("/folder/");
Mockito.when(request.getRequestURI()).thenReturn("/folder/");
filter.doFilter(request, response, chain);
Mockito.verify(resourceTypeChecker).typeOfResource("/folder/");
ArgumentCaptor<HttpServletRequest> wrappedReq = ArgumentCaptor.forClass(HttpServletRequest.class);
Mockito.verify(chain).doFilter(wrappedReq.capture(), Mockito.any(ServletResponse.class));
Assert.assertEquals("/folder/", wrappedReq.getValue().getRequestURI());
}
@Test
public void testMoveFolderRequest() throws IOException, ServletException {
Mockito.when(request.getPathInfo()).thenReturn("/folder");
Mockito.when(request.getRequestURI()).thenReturn("/folder");
Mockito.when(request.getMethod()).thenReturn("MOVE");
Mockito.when(request.getHeader("Destination")).thenReturn("/404");
filter.doFilter(request, response, chain);
ArgumentCaptor<HttpServletRequest> wrappedReq = ArgumentCaptor.forClass(HttpServletRequest.class);
Mockito.verify(chain).doFilter(wrappedReq.capture(), Mockito.any(ServletResponse.class));
Assert.assertEquals("/404/", wrappedReq.getValue().getHeader("Destination"));
}
/* UNKNOWN */
@Test
public void testUnknownPutRequest() throws IOException, ServletException {
Mockito.when(request.getPathInfo()).thenReturn("/404/");
Mockito.when(request.getRequestURI()).thenReturn("/404/");
Mockito.when(request.getMethod()).thenReturn("PUT");
filter.doFilter(request, response, chain);
Mockito.verify(resourceTypeChecker).typeOfResource("/404/");
ArgumentCaptor<HttpServletRequest> wrappedReq = ArgumentCaptor.forClass(HttpServletRequest.class);
Mockito.verify(chain).doFilter(wrappedReq.capture(), Mockito.any(ServletResponse.class));
Assert.assertEquals("/404", wrappedReq.getValue().getRequestURI());
}
@Test
public void testUnknownMkcolRequest() throws IOException, ServletException {
Mockito.when(request.getPathInfo()).thenReturn("/404");
Mockito.when(request.getRequestURI()).thenReturn("/404");
Mockito.when(request.getMethod()).thenReturn("MKCOL");
filter.doFilter(request, response, chain);
Mockito.verify(resourceTypeChecker).typeOfResource("/404");
ArgumentCaptor<HttpServletRequest> wrappedReq = ArgumentCaptor.forClass(HttpServletRequest.class);
Mockito.verify(chain).doFilter(wrappedReq.capture(), Mockito.any(ServletResponse.class));
Assert.assertEquals("/404/", wrappedReq.getValue().getRequestURI());
}
@Test
public void testUnknownPropfindRequest() throws IOException, ServletException {
Mockito.when(request.getPathInfo()).thenReturn("/404");
Mockito.when(request.getRequestURI()).thenReturn("/404");
Mockito.when(request.getMethod()).thenReturn("PROPFIND");
filter.doFilter(request, response, chain);
Mockito.verify(resourceTypeChecker).typeOfResource("/404");
ArgumentCaptor<HttpServletRequest> wrappedReq = ArgumentCaptor.forClass(HttpServletRequest.class);
Mockito.verify(chain).doFilter(wrappedReq.capture(), Mockito.any(ServletResponse.class));
Assert.assertSame(request, wrappedReq.getValue());
}
}
@@ -0,0 +1,65 @@
/*******************************************************************************
* Copyright (c) 2015 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.webdav.jackrabbitservlet;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.cryptomator.filesystem.FileSystem;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ThreadPool;
class FileSystemBasedWebDavServer {
private final Server server;
private final ServerConnector localConnector;
private final ContextHandlerCollection servletCollection;
public FileSystemBasedWebDavServer(FileSystem fileSystem) {
final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(10);
final ThreadPool tp = new QueuedThreadPool(4, 1, 1, queue);
server = new Server(tp);
localConnector = new ServerConnector(server);
localConnector.setHost("localhost");
localConnector.setPort(8080);
servletCollection = new ContextHandlerCollection();
final URI servletContextRoot1;
final URI servletContextRoot2;
try {
servletContextRoot1 = new URI("http", null, "localhost", 8080, "/foo/", null, null);
servletContextRoot2 = new URI("http", null, "localhost", 8080, "/bar/", null, null);
} catch (URISyntaxException e) {
throw new IllegalStateException(e);
}
final WebDavServletContextFactory servletContextFactory = new WebDavServletContextFactory();
servletCollection.addHandler(servletContextFactory.create(servletContextRoot1, fileSystem));
servletCollection.addHandler(servletContextFactory.create(servletContextRoot2, fileSystem));
servletCollection.mapContexts();
server.setConnectors(new Connector[] {localConnector});
server.setHandler(servletCollection);
}
public void start() throws Exception {
server.start();
}
public void stop() throws Exception {
server.stop();
}
}
@@ -0,0 +1,39 @@
/*******************************************************************************
* Copyright (c) 2015 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.webdav.jackrabbitservlet;
import java.nio.ByteBuffer;
import org.cryptomator.filesystem.FileSystem;
import org.cryptomator.filesystem.WritableFile;
import org.cryptomator.filesystem.inmem.InMemoryFileSystem;
public class InMemoryWebDavServer {
public static void main(String[] args) throws Exception {
FileSystem fileSystem = setupFilesystem();
FileSystemBasedWebDavServer server = new FileSystemBasedWebDavServer(fileSystem);
server.start();
System.out.println("Server started. Press any key to stop it...");
System.in.read();
server.stop();
}
private static FileSystem setupFilesystem() {
FileSystem fileSystem = new InMemoryFileSystem();
fileSystem.folder("mamals").folder("cats").create();
fileSystem.folder("mamals").folder("dogs").create();
try (WritableFile writable = fileSystem.folder("mamals").folder("cats").file("Garfield.txt").openWritable()) {
writable.write(ByteBuffer.wrap("meow".getBytes()));
}
return fileSystem;
}
}
@@ -0,0 +1,70 @@
/*******************************************************************************
* Copyright (c) 2015 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.webdav.jackrabbitservlet;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import org.cryptomator.filesystem.FileSystem;
import org.cryptomator.filesystem.nio.NioFileSystem;
public class NioWebDavServer {
private static final String PATH_TO_SERVE_PROPERTY = "pathToServe";
public static void main(String[] args) throws Exception {
FileSystem fileSystem = setupFilesystem();
FileSystemBasedWebDavServer server = new FileSystemBasedWebDavServer(fileSystem);
server.start();
System.out.println("Server started. Press any key to stop it...");
System.in.read();
server.stop();
}
private static FileSystem setupFilesystem() {
return NioFileSystem.rootedAt(pathToServe());
}
private static Path pathToServe() {
Path path = pathFromSystemProperty().orElseGet(NioWebDavServer::pathEnteredByUser);
if (!Files.isDirectory(path)) {
throw new RuntimeException("Path is not a directory");
}
System.out.println("Serving " + path);
return path;
}
private static Optional<Path> pathFromSystemProperty() {
String value = System.getProperty(PATH_TO_SERVE_PROPERTY);
if (value == null) {
return Optional.empty();
} else {
return Optional.of(Paths.get(value));
}
}
private static Path pathEnteredByUser() {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in, Charset.defaultCharset()));
System.out.print("Enter absolute path to serve (must be an existing directory): ");
try {
return Paths.get(in.readLine());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Copyright (c) 2014 Markus Kreusch
This file is licensed under the terms of the MIT license.
See the LICENSE.txt file for more info.
Contributors:
Sebastian Stenzel - log4j config for WebDAV unit tests
-->
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n" />
<ThresholdFilter level="WARN" onMatch="DENY" onMismatch="ACCEPT" />
</Console>
<Console name="StdErr" target="SYSTEM_ERR">
<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n" />
<ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY" />
</Console>
</Appenders>
<Loggers>
<!-- show our own debug messages: -->
<Logger name="org.cryptomator" level="DEBUG" />
<!-- mute dependencies: -->
<Root level="INFO">
<AppenderRef ref="Console" />
<AppenderRef ref="StdErr" />
</Root>
</Loggers>
</Configuration>