refactored directory structure, so windows (and OneDrive) can handle vaults better

This commit is contained in:
Sebastian Stenzel
2015-04-28 18:19:05 +02:00
parent a6972f62f2
commit cdf9c28a38
31 changed files with 888 additions and 728 deletions
-4
View File
@@ -64,9 +64,5 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
</dependency>
</dependencies>
</project>
@@ -14,8 +14,8 @@ public class IORuntimeException extends RuntimeException {
private static final long serialVersionUID = -4713080133052143303L;
public IORuntimeException(IOException ioException) {
super(ioException);
public IORuntimeException(IOException cause) {
super(cause);
}
@Override
@@ -9,11 +9,9 @@
package org.cryptomator.webdav.jackrabbit;
import java.io.IOException;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.FileTime;
import java.util.List;
@@ -21,7 +19,6 @@ import java.util.List;
import org.apache.commons.io.FilenameUtils;
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.DavServletResponse;
import org.apache.jackrabbit.webdav.DavSession;
@@ -46,14 +43,14 @@ abstract class AbstractEncryptedNode implements DavResource {
private static final Logger LOG = LoggerFactory.getLogger(AbstractEncryptedNode.class);
private static final String DAV_COMPLIANCE_CLASSES = "1, 2";
protected final DavResourceFactory factory;
protected final DavResourceLocator locator;
protected final CryptoResourceFactory factory;
protected final CryptoLocator locator;
protected final DavSession session;
protected final LockManager lockManager;
protected final Cryptor cryptor;
protected final DavPropertySet properties;
protected AbstractEncryptedNode(DavResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor) {
protected AbstractEncryptedNode(CryptoResourceFactory factory, CryptoLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor) {
this.factory = factory;
this.locator = locator;
this.session = session;
@@ -63,6 +60,8 @@ abstract class AbstractEncryptedNode implements DavResource {
this.determineProperties();
}
protected abstract Path getPhysicalPath();
@Override
public String getComplianceClass() {
return DAV_COMPLIANCE_CLASSES;
@@ -75,8 +74,7 @@ abstract class AbstractEncryptedNode implements DavResource {
@Override
public boolean exists() {
final Path path = ResourcePathUtils.getPhysicalPath(this);
return Files.exists(path);
return Files.exists(getPhysicalPath());
}
@Override
@@ -91,7 +89,7 @@ abstract class AbstractEncryptedNode implements DavResource {
}
@Override
public DavResourceLocator getLocator() {
public CryptoLocator getLocator() {
return locator;
}
@@ -107,9 +105,8 @@ abstract class AbstractEncryptedNode implements DavResource {
@Override
public long getModificationTime() {
final Path path = ResourcePathUtils.getPhysicalPath(this);
try {
return Files.getLastModifiedTime(path).toMillis();
return Files.getLastModifiedTime(getPhysicalPath()).toMillis();
} catch (IOException e) {
return -1;
}
@@ -139,7 +136,7 @@ abstract class AbstractEncryptedNode implements DavResource {
LOG.info("Set property {}", property.getName());
try {
final Path path = ResourcePathUtils.getPhysicalPath(this);
final Path path = getPhysicalPath();
if (DavPropertyName.CREATIONDATE.equals(property.getName()) && property.getValue() instanceof String) {
final String createDateStr = (String) property.getValue();
final FileTime createTime = FileTimeUtils.fromRfc1123String(createDateStr);
@@ -196,49 +193,37 @@ abstract class AbstractEncryptedNode implements DavResource {
}
@Override
public void move(DavResource dest) throws DavException {
final Path src = ResourcePathUtils.getPhysicalPath(this);
final Path dst = ResourcePathUtils.getPhysicalPath(dest);
try {
// check for conflicts:
if (Files.exists(dst) && Files.getLastModifiedTime(dst).toMillis() > Files.getLastModifiedTime(src).toMillis()) {
throw new DavException(DavServletResponse.SC_CONFLICT, "File at destination already exists: " + dst.toString());
}
// move:
public final void move(DavResource dest) throws DavException {
if (dest instanceof AbstractEncryptedNode) {
try {
Files.move(src, dst, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
} catch (AtomicMoveNotSupportedException e) {
Files.move(src, dst, StandardCopyOption.REPLACE_EXISTING);
this.move((AbstractEncryptedNode) dest);
} catch (IOException e) {
LOG.error("Error moving file from " + this.getResourcePath() + " to " + dest.getResourcePath());
throw new IORuntimeException(e);
}
} catch (IOException e) {
LOG.error("Error moving file from " + src.toString() + " to " + dst.toString());
throw new IORuntimeException(e);
} else {
throw new IllegalArgumentException("Unsupported resource type: " + dest.getClass().getName());
}
}
public abstract void move(AbstractEncryptedNode dest) throws DavException, IOException;
@Override
public void copy(DavResource dest, boolean shallow) throws DavException {
final Path src = ResourcePathUtils.getPhysicalPath(this);
final Path dst = ResourcePathUtils.getPhysicalPath(dest);
try {
// check for conflicts:
if (Files.exists(dst) && Files.getLastModifiedTime(dst).toMillis() > Files.getLastModifiedTime(src).toMillis()) {
throw new DavException(DavServletResponse.SC_CONFLICT, "File at destination already exists: " + dst.toString());
}
// copy:
public final void copy(DavResource dest, boolean shallow) throws DavException {
if (dest instanceof AbstractEncryptedNode) {
try {
Files.copy(src, dst, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
} catch (AtomicMoveNotSupportedException e) {
Files.copy(src, dst, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING);
this.copy((AbstractEncryptedNode) dest, shallow);
} catch (IOException e) {
LOG.error("Error copying file from " + this.getResourcePath() + " to " + dest.getResourcePath());
throw new IORuntimeException(e);
}
} catch (IOException e) {
LOG.error("Error copying file from " + src.toString() + " to " + dst.toString());
throw new IORuntimeException(e);
} else {
throw new IllegalArgumentException("Unsupported resource type: " + dest.getClass().getName());
}
}
public abstract void copy(AbstractEncryptedNode dest, boolean shallow) throws DavException, IOException;
@Override
public boolean isLockable(Type type, Scope scope) {
return true;
@@ -281,7 +266,7 @@ abstract class AbstractEncryptedNode implements DavResource {
}
@Override
public DavResourceFactory getFactory() {
public CryptoResourceFactory getFactory() {
return factory;
}
@@ -1,24 +0,0 @@
package org.cryptomator.webdav.jackrabbit;
import java.util.Map;
import org.apache.commons.collections4.BidiMap;
import org.apache.commons.collections4.bidimap.AbstractDualBidiMap;
import org.apache.commons.collections4.map.LRUMap;
final class BidiLRUMap<K, V> extends AbstractDualBidiMap<K, V> {
BidiLRUMap(int maxSize) {
super(new LRUMap<K, V>(maxSize), new LRUMap<V, K>(maxSize));
}
protected BidiLRUMap(final Map<K, V> normalMap, final Map<V, K> reverseMap, final BidiMap<V, K> inverseBidiMap) {
super(normalMap, reverseMap, inverseBidiMap);
}
@Override
protected BidiMap<V, K> createBidiMap(Map<V, K> normalMap, Map<K, V> reverseMap, BidiMap<K, V> inverseMap) {
return new BidiLRUMap<V, K>(normalMap, reverseMap, inverseMap);
}
}
@@ -0,0 +1,166 @@
package org.cryptomator.webdav.jackrabbit;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.jackrabbit.webdav.DavResourceLocator;
import org.apache.jackrabbit.webdav.util.EncodeUtil;
import org.apache.logging.log4j.util.Strings;
import org.cryptomator.crypto.Cryptor;
import org.cryptomator.webdav.exceptions.IORuntimeException;
class CryptoLocator implements DavResourceLocator {
private final CryptoLocatorFactory factory;
private final Cryptor cryptor;
private final Path rootPath;
private final String prefix;
private final String resourcePath;
public CryptoLocator(CryptoLocatorFactory factory, Cryptor cryptor, Path rootPath, String prefix, String resourcePath) {
this.factory = factory;
this.cryptor = cryptor;
this.rootPath = rootPath;
this.prefix = prefix;
this.resourcePath = FilenameUtils.normalizeNoEndSeparator(resourcePath, true);
}
/* path variants */
/**
* Returns the decrypted path without any trailing slash.
*
* @see #getHref(boolean)
* @return Plaintext resource path.
*/
@Override
public String getResourcePath() {
return resourcePath;
}
/**
* Returns the decrypted path and adds URL-encoding.
*
* @param isCollection If true, a trailing slash will be appended.
* @see #getResourcePath()
* @return URL-encoded plaintext resource path.
*/
@Override
public String getHref(boolean isCollection) {
final String encodedResourcePath = EncodeUtil.escapePath(getResourcePath());
final String href = getPrefix().concat(encodedResourcePath);
assert !href.endsWith("/");
if (isCollection) {
return href.concat("/");
} else {
return href;
}
}
/**
* Returns the encrypted, absolute path on the local filesystem.
*
* @return Absolute, encrypted path as string (use {@link #getEncryptedFilePath()} for {@link Path}s).
*/
@Override
public String getRepositoryPath() {
if (isRootLocation()) {
return getDirectoryPath();
}
try {
final String plaintextPath = getResourcePath();
final String plaintextDir = FilenameUtils.getPathNoEndSeparator(plaintextPath);
final String plaintextFilename = FilenameUtils.getName(plaintextPath);
final String ciphertextDir = cryptor.encryptDirectoryPath(plaintextDir, FileSystems.getDefault().getSeparator());
final String ciphertextFilename = cryptor.encryptFilename(plaintextFilename, factory);
final String ciphertextPath = ciphertextDir + FileSystems.getDefault().getSeparator() + ciphertextFilename;
return rootPath.resolve(ciphertextPath).toString();
} catch (IOException e) {
throw new IORuntimeException(e);
}
}
/**
* Returns the encrypted, absolute path on the local filesystem to the directory represented by this locator.
*
* @return Absolute, encrypted path as string (use {@link #getEncryptedDirectoryPath()} for {@link Path}s).
*/
public String getDirectoryPath() {
final String ciphertextPath = cryptor.encryptDirectoryPath(getResourcePath(), FileSystems.getDefault().getSeparator());
return rootPath.resolve(ciphertextPath).toString();
}
public Path getEncryptedFilePath() {
return FileSystems.getDefault().getPath(getRepositoryPath());
}
public Path getEncryptedDirectoryPath() {
return FileSystems.getDefault().getPath(getDirectoryPath());
}
/* other stuff */
@Override
public String getPrefix() {
return prefix;
}
@Override
public String getWorkspacePath() {
return isRootLocation() ? null : "";
}
@Override
public String getWorkspaceName() {
return getPrefix();
}
@Override
public boolean isSameWorkspace(DavResourceLocator locator) {
return (locator == null) ? false : isSameWorkspace(locator.getWorkspaceName());
}
@Override
public boolean isSameWorkspace(String workspaceName) {
return getWorkspaceName().equals(workspaceName);
}
@Override
public boolean isRootLocation() {
return Strings.isEmpty(getResourcePath());
}
@Override
public CryptoLocatorFactory getFactory() {
return factory;
}
/* hashcode and equals */
@Override
public int hashCode() {
final HashCodeBuilder builder = new HashCodeBuilder();
builder.append(prefix);
builder.append(resourcePath);
return builder.toHashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof CryptoLocator) {
final CryptoLocator other = (CryptoLocator) obj;
final EqualsBuilder builder = new EqualsBuilder();
builder.append(this.factory, other.factory);
builder.append(this.prefix, other.prefix);
builder.append(this.resourcePath, other.resourcePath);
return builder.isEquals();
} else {
return false;
}
}
}
@@ -0,0 +1,92 @@
package org.cryptomator.webdav.jackrabbit;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jackrabbit.webdav.DavLocatorFactory;
import org.apache.jackrabbit.webdav.DavResourceLocator;
import org.apache.jackrabbit.webdav.util.EncodeUtil;
import org.cryptomator.crypto.Cryptor;
import org.cryptomator.crypto.CryptorMetadataSupport;
import org.cryptomator.crypto.exceptions.DecryptFailedException;
import org.cryptomator.webdav.exceptions.DecryptFailedRuntimeException;
import org.cryptomator.webdav.exceptions.IORuntimeException;
class CryptoLocatorFactory implements DavLocatorFactory, CryptorMetadataSupport {
private final Path dataRoot;
private final Path metadataRoot;
private final Cryptor cryptor;
CryptoLocatorFactory(String fsRoot, Cryptor cryptor) {
this.dataRoot = FileSystems.getDefault().getPath(fsRoot).resolve("d");
this.metadataRoot = FileSystems.getDefault().getPath(fsRoot).resolve("m");
this.cryptor = cryptor;
}
@Override
public CryptoLocator createResourceLocator(String prefix, String href) {
final String fullPrefix = prefix.endsWith("/") ? prefix : prefix + "/";
final String relativeHref = StringUtils.removeStart(href, fullPrefix);
final String resourcePath = EncodeUtil.unescape(StringUtils.removeStart(relativeHref, "/"));
return new CryptoLocator(this, cryptor, dataRoot, fullPrefix, resourcePath);
}
/**
* @throws DecryptFailedRuntimeException, which should be a checked exception, but Jackrabbit doesn't allow that.
*/
@Override
public CryptoLocator createResourceLocator(String prefix, String workspacePath, String path, boolean isResourcePath) {
if (!isResourcePath) {
throw new UnsupportedOperationException("Can not decrypt " + path + " without knowing plaintext parent path.");
}
final String fullPrefix = prefix.endsWith("/") ? prefix : prefix + "/";
return new CryptoLocator(this, cryptor, dataRoot, fullPrefix, path);
}
@Override
public CryptoLocator createResourceLocator(String prefix, String workspacePath, String resourcePath) {
try {
return createResourceLocator(prefix, workspacePath, resourcePath, true);
} catch (DecryptFailedRuntimeException e) {
throw new IllegalStateException("Tried to decrypt resourcePath. Only repositoryPaths can be encrypted.", e);
}
}
public DavResourceLocator createSubresourceLocator(CryptoLocator parentResource, String ciphertextChildName) {
try {
final String plaintextFilename = cryptor.decryptFilename(ciphertextChildName, this);
final String plaintextPath = FilenameUtils.concat(parentResource.getResourcePath(), plaintextFilename);
return createResourceLocator(parentResource.getPrefix(), parentResource.getWorkspacePath(), plaintextPath);
} catch (IOException e) {
throw new IORuntimeException(e);
} catch (DecryptFailedException e) {
throw new DecryptFailedRuntimeException(e);
}
}
/* metadata storage */
@Override
public void writeMetadata(String metadataGroup, byte[] encryptedMetadata) throws IOException {
final Path metaDataFile = metadataRoot.resolve(metadataGroup);
Files.write(metaDataFile, encryptedMetadata, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.DSYNC);
}
@Override
public byte[] readMetadata(String metadataGroup) throws IOException {
final Path metaDataFile = metadataRoot.resolve(metadataGroup);
if (!Files.isReadable(metaDataFile)) {
return null;
} else {
return Files.readAllBytes(metaDataFile);
}
}
}
@@ -0,0 +1,99 @@
package org.cryptomator.webdav.jackrabbit;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.ExecutorService;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.DavMethods;
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.crypto.Cryptor;
import org.eclipse.jetty.http.HttpHeader;
public class CryptoResourceFactory implements DavResourceFactory {
private final LockManager lockManager = new SimpleLockManager();
private final Cryptor cryptor;
private final CryptoWarningHandler cryptoWarningHandler;
private final ExecutorService backgroundTaskExecutor;
CryptoResourceFactory(Cryptor cryptor, CryptoWarningHandler cryptoWarningHandler, ExecutorService backgroundTaskExecutor) {
this.cryptor = cryptor;
this.cryptoWarningHandler = cryptoWarningHandler;
this.backgroundTaskExecutor = backgroundTaskExecutor;
}
@Override
public final DavResource createResource(DavResourceLocator locator, DavServletRequest request, DavServletResponse response) throws DavException {
if (locator instanceof CryptoLocator) {
return createResource((CryptoLocator) locator, request, response);
} else {
throw new IllegalArgumentException("Unsupported resource locator of type " + locator.getClass().getName());
}
}
@Override
public final DavResource createResource(DavResourceLocator locator, DavSession session) throws DavException {
if (locator instanceof CryptoLocator) {
return createResource((CryptoLocator) locator, session);
} else {
throw new IllegalArgumentException("Unsupported resource locator of type " + locator.getClass().getName());
}
}
private DavResource createResource(CryptoLocator locator, DavServletRequest request, DavServletResponse response) throws DavException {
final Path filepath = FileSystems.getDefault().getPath(locator.getRepositoryPath());
final Path dirpath = FileSystems.getDefault().getPath(locator.getDirectoryPath());
final String rangeHeader = request.getHeader(HttpHeader.RANGE.asString());
if (Files.isDirectory(dirpath) || DavMethods.METHOD_MKCOL.equals(request.getMethod())) {
return createDirectory(locator, request.getDavSession());
} else if (Files.isRegularFile(filepath) && DavMethods.METHOD_GET.equals(request.getMethod()) && rangeHeader != null) {
response.setStatus(HttpStatus.SC_PARTIAL_CONTENT);
return createFilePart(locator, request.getDavSession(), request);
} else if (Files.isRegularFile(filepath) || DavMethods.METHOD_PUT.equals(request.getMethod())) {
return createFile(locator, request.getDavSession());
} else {
return createNonExisting(locator, request.getDavSession());
}
}
private DavResource createResource(CryptoLocator locator, DavSession session) throws DavException {
final Path filepath = FileSystems.getDefault().getPath(locator.getRepositoryPath());
final Path dirpath = FileSystems.getDefault().getPath(locator.getDirectoryPath());
if (Files.isDirectory(dirpath)) {
return createDirectory(locator, session);
} else if (Files.isRegularFile(filepath)) {
return createFile(locator, session);
} else {
return createNonExisting(locator, session);
}
}
private EncryptedFile createFilePart(CryptoLocator locator, DavSession session, DavServletRequest request) {
return new EncryptedFilePart(this, locator, session, request, lockManager, cryptor, cryptoWarningHandler, backgroundTaskExecutor);
}
private EncryptedFile createFile(CryptoLocator locator, DavSession session) {
return new EncryptedFile(this, locator, session, lockManager, cryptor, cryptoWarningHandler);
}
private EncryptedDir createDirectory(CryptoLocator locator, DavSession session) {
return new EncryptedDir(this, locator, session, lockManager, cryptor);
}
private NonExistingNode createNonExisting(CryptoLocator locator, DavSession session) {
return new NonExistingNode(this, locator, session, lockManager, cryptor);
}
}
@@ -1,242 +0,0 @@
/*******************************************************************************
* Copyright (c) 2014 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.jackrabbit;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import org.apache.commons.collections4.BidiMap;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.jackrabbit.webdav.DavLocatorFactory;
import org.apache.jackrabbit.webdav.DavResourceLocator;
import org.apache.jackrabbit.webdav.util.EncodeUtil;
import org.cryptomator.crypto.Cryptor;
import org.cryptomator.crypto.CryptorIOSupport;
import org.cryptomator.crypto.SensitiveDataSwipeListener;
import org.cryptomator.crypto.exceptions.DecryptFailedException;
import org.cryptomator.webdav.exceptions.DecryptFailedRuntimeException;
class DavLocatorFactoryImpl implements DavLocatorFactory, SensitiveDataSwipeListener, CryptorIOSupport {
private static final int MAX_CACHED_PATHS = 10000;
private final Path fsRoot;
private final Cryptor cryptor;
private final BidiMap<String, String> pathCache = new BidiLRUMap<>(MAX_CACHED_PATHS); // <decryptedPath, encryptedPath>
DavLocatorFactoryImpl(String fsRoot, Cryptor cryptor) {
this.fsRoot = FileSystems.getDefault().getPath(fsRoot);
this.cryptor = cryptor;
cryptor.addSensitiveDataSwipeListener(this);
}
/* DavLocatorFactory */
@Override
public DavResourceLocator createResourceLocator(String prefix, String href) {
final String fullPrefix = prefix.endsWith("/") ? prefix : prefix + "/";
final String relativeHref = StringUtils.removeStart(href, fullPrefix);
final String resourcePath = EncodeUtil.unescape(StringUtils.removeStart(relativeHref, "/"));
return new DavResourceLocatorImpl(fullPrefix, resourcePath);
}
/**
* @throws DecryptFailedRuntimeException, which should a checked exception, but Jackrabbit doesn't allow that.
*/
@Override
public DavResourceLocator createResourceLocator(String prefix, String workspacePath, String path, boolean isResourcePath) {
final String fullPrefix = prefix.endsWith("/") ? prefix : prefix + "/";
try {
final String resourcePath = (isResourcePath) ? path : getResourcePath(path);
return new DavResourceLocatorImpl(fullPrefix, resourcePath);
} catch (DecryptFailedException e) {
throw new DecryptFailedRuntimeException(e);
}
}
@Override
public DavResourceLocator createResourceLocator(String prefix, String workspacePath, String resourcePath) {
try {
return createResourceLocator(prefix, workspacePath, resourcePath, true);
} catch (DecryptFailedRuntimeException e) {
throw new IllegalStateException("Tried to decrypt resourcePath. Only repositoryPaths can be encrypted.", e);
}
}
/* Encryption/Decryption */
/**
* @return Encrypted absolute paths on the file system.
*/
private String getRepositoryPath(String resourcePath) {
String encryptedPath = pathCache.get(resourcePath);
if (encryptedPath == null) {
encryptedPath = encryptRepositoryPath(resourcePath);
pathCache.put(resourcePath, encryptedPath);
}
return encryptedPath;
}
private String encryptRepositoryPath(String resourcePath) {
if (resourcePath == null) {
return fsRoot.toString();
}
final String encryptedRepoPath = cryptor.encryptPath(resourcePath, FileSystems.getDefault().getSeparator().charAt(0), '/', this);
return fsRoot.resolve(encryptedRepoPath).toString();
}
/**
* @return Decrypted path for use in URIs.
*/
private String getResourcePath(String repositoryPath) throws DecryptFailedException {
String decryptedPath = pathCache.getKey(repositoryPath);
if (decryptedPath == null) {
decryptedPath = decryptResourcePath(repositoryPath);
pathCache.put(decryptedPath, repositoryPath);
}
return decryptedPath;
}
private String decryptResourcePath(String repositoryPath) throws DecryptFailedException {
final Path absRepoPath = FileSystems.getDefault().getPath(repositoryPath);
if (fsRoot.equals(absRepoPath)) {
return null;
} else {
final Path relativeRepositoryPath = fsRoot.relativize(absRepoPath);
final String resourcePath = cryptor.decryptPath(relativeRepositoryPath.toString(), FileSystems.getDefault().getSeparator().charAt(0), '/', this);
return resourcePath;
}
}
/* CryptorIOSupport */
@Override
public void writePathSpecificMetadata(String encryptedPath, byte[] encryptedMetadata) throws IOException {
final Path metaDataFile = fsRoot.resolve(encryptedPath);
Files.write(metaDataFile, encryptedMetadata, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.DSYNC);
}
@Override
public byte[] readPathSpecificMetadata(String encryptedPath) throws IOException {
final Path metaDataFile = fsRoot.resolve(encryptedPath);
if (!Files.isReadable(metaDataFile)) {
return null;
} else {
return Files.readAllBytes(metaDataFile);
}
}
/* SensitiveDataSwipeListener */
@Override
public void swipeSensitiveData() {
pathCache.clear();
}
/* Locator */
private class DavResourceLocatorImpl implements DavResourceLocator {
private final String prefix;
private final String resourcePath;
private DavResourceLocatorImpl(String prefix, String resourcePath) {
this.prefix = prefix;
this.resourcePath = FilenameUtils.normalizeNoEndSeparator(resourcePath, true);
}
@Override
public String getPrefix() {
return prefix;
}
@Override
public String getResourcePath() {
return resourcePath;
}
@Override
public String getWorkspacePath() {
return isRootLocation() ? null : "";
}
@Override
public String getWorkspaceName() {
return getPrefix();
}
@Override
public boolean isSameWorkspace(DavResourceLocator locator) {
return (locator == null) ? false : isSameWorkspace(locator.getWorkspaceName());
}
@Override
public boolean isSameWorkspace(String workspaceName) {
return getWorkspaceName().equals(workspaceName);
}
@Override
public String getHref(boolean isCollection) {
final String encodedResourcePath = EncodeUtil.escapePath(getResourcePath());
final String href = getPrefix().concat(encodedResourcePath);
if (isCollection && !href.endsWith("/")) {
return href.concat("/");
} else if (!isCollection && href.endsWith("/")) {
return href.substring(0, href.length() - 1);
} else {
return href;
}
}
@Override
public boolean isRootLocation() {
return getResourcePath() == null;
}
@Override
public DavLocatorFactory getFactory() {
return DavLocatorFactoryImpl.this;
}
@Override
public String getRepositoryPath() {
return DavLocatorFactoryImpl.this.getRepositoryPath(getResourcePath());
}
@Override
public int hashCode() {
final HashCodeBuilder builder = new HashCodeBuilder();
builder.append(prefix);
builder.append(resourcePath);
return builder.toHashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof DavResourceLocatorImpl) {
final DavResourceLocatorImpl other = (DavResourceLocatorImpl) obj;
final EqualsBuilder builder = new EqualsBuilder();
builder.append(this.prefix, other.prefix);
builder.append(this.resourcePath, other.resourcePath);
return builder.isEquals();
} else {
return false;
}
}
}
}
@@ -1,88 +0,0 @@
/*******************************************************************************
* Copyright (c) 2014 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.jackrabbit;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.ExecutorService;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.DavMethods;
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.crypto.Cryptor;
import org.eclipse.jetty.http.HttpHeader;
class DavResourceFactoryImpl implements DavResourceFactory {
private final LockManager lockManager = new SimpleLockManager();
private final Cryptor cryptor;
private final CryptoWarningHandler cryptoWarningHandler;
private final ExecutorService backgroundTaskExecutor;
DavResourceFactoryImpl(Cryptor cryptor, CryptoWarningHandler cryptoWarningHandler, ExecutorService backgroundTaskExecutor) {
this.cryptor = cryptor;
this.cryptoWarningHandler = cryptoWarningHandler;
this.backgroundTaskExecutor = backgroundTaskExecutor;
}
@Override
public DavResource createResource(DavResourceLocator locator, DavServletRequest request, DavServletResponse response) throws DavException {
final Path path = ResourcePathUtils.getPhysicalPath(locator);
final String rangeHeader = request.getHeader(HttpHeader.RANGE.asString());
if (Files.isRegularFile(path) && DavMethods.METHOD_GET.equals(request.getMethod()) && rangeHeader != null) {
response.setStatus(HttpStatus.SC_PARTIAL_CONTENT);
return createFilePart(locator, request.getDavSession(), request);
} else if (Files.isRegularFile(path) || DavMethods.METHOD_PUT.equals(request.getMethod())) {
return createFile(locator, request.getDavSession());
} else if (Files.isDirectory(path) || DavMethods.METHOD_MKCOL.equals(request.getMethod())) {
return createDirectory(locator, request.getDavSession());
} else {
return createNonExisting(locator, request.getDavSession());
}
}
@Override
public DavResource createResource(DavResourceLocator locator, DavSession session) throws DavException {
final Path path = ResourcePathUtils.getPhysicalPath(locator);
if (path != null && Files.isRegularFile(path)) {
return createFile(locator, session);
} else if (path != null && Files.isDirectory(path)) {
return createDirectory(locator, session);
} else {
return createNonExisting(locator, session);
}
}
private EncryptedFile createFilePart(DavResourceLocator locator, DavSession session, DavServletRequest request) {
return new EncryptedFilePart(this, locator, session, request, lockManager, cryptor, cryptoWarningHandler, backgroundTaskExecutor);
}
private EncryptedFile createFile(DavResourceLocator locator, DavSession session) {
return new EncryptedFile(this, locator, session, lockManager, cryptor, cryptoWarningHandler);
}
private EncryptedDir createDirectory(DavResourceLocator locator, DavSession session) {
return new EncryptedDir(this, locator, session, lockManager, cryptor);
}
private NonExistingNode createNonExisting(DavResourceLocator locator, DavSession session) {
return new NonExistingNode(this, locator, session, lockManager, cryptor);
}
}
@@ -10,11 +10,11 @@ package org.cryptomator.webdav.jackrabbit;
import java.io.IOException;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.DirectoryStream;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
@@ -23,7 +23,6 @@ import java.util.List;
import org.apache.commons.io.IOUtils;
import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.DavResource;
import org.apache.jackrabbit.webdav.DavResourceFactory;
import org.apache.jackrabbit.webdav.DavResourceIterator;
import org.apache.jackrabbit.webdav.DavResourceIteratorImpl;
import org.apache.jackrabbit.webdav.DavResourceLocator;
@@ -48,28 +47,55 @@ class EncryptedDir extends AbstractEncryptedNode {
private static final Logger LOG = LoggerFactory.getLogger(EncryptedDir.class);
public EncryptedDir(DavResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor) {
public EncryptedDir(CryptoResourceFactory factory, CryptoLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor) {
super(factory, locator, session, lockManager, cryptor);
}
@Override
protected Path getPhysicalPath() {
return locator.getEncryptedDirectoryPath();
}
@Override
public boolean isCollection() {
return true;
}
@Override
public void addMember(DavResource resource, InputContext inputContext) throws DavException {
if (resource.isCollection()) {
this.addMemberDir(resource, inputContext);
} else {
this.addMemberFile(resource, inputContext);
public boolean exists() {
return Files.isDirectory(locator.getEncryptedDirectoryPath());
}
@Override
public long getModificationTime() {
try {
return Files.getLastModifiedTime(locator.getEncryptedDirectoryPath()).toMillis();
} catch (IOException e) {
return -1;
}
}
private void addMemberDir(DavResource resource, InputContext inputContext) throws DavException {
final Path childPath = ResourcePathUtils.getPhysicalPath(resource);
@Override
public void addMember(DavResource resource, InputContext inputContext) throws DavException {
if (resource instanceof AbstractEncryptedNode) {
addMember((AbstractEncryptedNode) resource, inputContext);
} else {
throw new IllegalArgumentException("Unsupported resource type: " + resource.getClass().getName());
}
}
private void addMember(AbstractEncryptedNode childResource, InputContext inputContext) throws DavException {
if (childResource.isCollection()) {
this.addMemberDir(childResource.getLocator(), inputContext);
} else {
this.addMemberFile(childResource.getLocator(), inputContext);
}
}
private void addMemberDir(CryptoLocator childLocator, InputContext inputContext) throws DavException {
try {
Files.createDirectories(childPath);
Files.createDirectories(childLocator.getEncryptedFilePath());
Files.createDirectories(childLocator.getEncryptedDirectoryPath());
} catch (SecurityException e) {
throw new DavException(DavServletResponse.SC_FORBIDDEN, e);
} catch (IOException e) {
@@ -78,9 +104,8 @@ class EncryptedDir extends AbstractEncryptedNode {
}
}
private void addMemberFile(DavResource resource, InputContext inputContext) throws DavException {
final Path childPath = ResourcePathUtils.getPhysicalPath(resource);
try (final SeekableByteChannel channel = Files.newByteChannel(childPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
private void addMemberFile(CryptoLocator childLocator, InputContext inputContext) throws DavException {
try (final SeekableByteChannel channel = Files.newByteChannel(childLocator.getEncryptedFilePath(), StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
cryptor.encryptFile(inputContext.getInputStream(), channel);
} catch (SecurityException e) {
throw new DavException(DavServletResponse.SC_FORBIDDEN, e);
@@ -100,14 +125,15 @@ class EncryptedDir extends AbstractEncryptedNode {
@Override
public DavResourceIterator getMembers() {
final Path dir = ResourcePathUtils.getPhysicalPath(this);
try {
final DirectoryStream<Path> directoryStream = Files.newDirectoryStream(dir, cryptor.getPayloadFilesFilter());
final DirectoryStream<Path> directoryStream = Files.newDirectoryStream(locator.getEncryptedDirectoryPath(), cryptor.getPayloadFilesFilter());
final List<DavResource> result = new ArrayList<>();
for (final Path childPath : directoryStream) {
try {
final DavResourceLocator childLocator = locator.getFactory().createResourceLocator(locator.getPrefix(), locator.getWorkspacePath(), childPath.toString(), false);
final DavResourceLocator childLocator = locator.getFactory().createSubresourceLocator(locator, childPath.getFileName().toString());
// final DavResourceLocator childLocator = locator.getFactory().createResourceLocator(locator.getPrefix(),
// locator.getWorkspacePath(), childPath.toString(), false);
final DavResource resource = factory.createResource(childLocator, session);
result.add(resource);
} catch (DecryptFailedRuntimeException e) {
@@ -126,19 +152,80 @@ class EncryptedDir extends AbstractEncryptedNode {
}
@Override
public void removeMember(DavResource member) throws DavException {
final Path memberPath = ResourcePathUtils.getPhysicalPath(member);
public void removeMember(DavResource member) {
if (member instanceof AbstractEncryptedNode) {
removeMember((AbstractEncryptedNode) member);
} else {
throw new IllegalArgumentException("Unsupported resource type: " + member.getClass().getName());
}
}
private void removeMember(AbstractEncryptedNode member) {
try {
if (Files.exists(memberPath)) {
Files.walkFileTree(memberPath, new DeletingFileVisitor());
if (member.isCollection()) {
member.getMembers().forEachRemaining(m -> securelyRemoveMemberOfCollection(member, m));
Files.deleteIfExists(member.getLocator().getEncryptedDirectoryPath());
}
} catch (SecurityException e) {
throw new DavException(DavServletResponse.SC_FORBIDDEN, e);
Files.deleteIfExists(member.getLocator().getEncryptedFilePath());
} catch (IOException e) {
throw new IORuntimeException(e);
}
}
private void securelyRemoveMemberOfCollection(DavResource collection, DavResource member) {
try {
collection.removeMember(member);
} catch (DavException e) {
throw new IllegalStateException("DavException should not be thrown by collections of type EncryptedDir. Collections is of type " + collection.getClass().getName());
}
}
@Override
public void move(AbstractEncryptedNode dest) throws DavException, IOException {
final Path srcDir = this.locator.getEncryptedDirectoryPath();
final Path dstDir = dest.locator.getEncryptedDirectoryPath();
final Path srcFile = this.locator.getEncryptedFilePath();
final Path dstFile = dest.locator.getEncryptedFilePath();
// check for conflicts:
if (Files.exists(dstDir) && Files.getLastModifiedTime(dstDir).toMillis() > Files.getLastModifiedTime(dstDir).toMillis()) {
throw new DavException(DavServletResponse.SC_CONFLICT, "Directory at destination already exists: " + dstDir.toString());
}
// move:
Files.createDirectories(dstDir);
try {
Files.move(srcDir, dstDir, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
Files.move(srcFile, dstFile, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
} catch (AtomicMoveNotSupportedException e) {
Files.move(srcDir, dstDir, StandardCopyOption.REPLACE_EXISTING);
Files.move(srcFile, dstFile, StandardCopyOption.REPLACE_EXISTING);
}
}
@Override
public void copy(AbstractEncryptedNode dest, boolean shallow) throws DavException, IOException {
final Path srcDir = this.locator.getEncryptedDirectoryPath();
final Path dstDir = dest.locator.getEncryptedDirectoryPath();
final Path srcFile = this.locator.getEncryptedFilePath();
final Path dstFile = dest.locator.getEncryptedFilePath();
// check for conflicts:
if (Files.exists(dstDir) && Files.getLastModifiedTime(dstDir).toMillis() > Files.getLastModifiedTime(dstDir).toMillis()) {
throw new DavException(DavServletResponse.SC_CONFLICT, "Directory at destination already exists: " + dstDir.toString());
}
// copy:
Files.createDirectories(dstDir);
try {
Files.copy(srcDir, dstDir, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
Files.copy(srcFile, dstFile, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
} catch (AtomicMoveNotSupportedException e) {
Files.copy(srcDir, dstDir, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING);
Files.copy(srcFile, dstFile, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING);
}
}
@Override
public void spool(OutputContext outputContext) throws IOException {
// do nothing
@@ -146,7 +233,7 @@ class EncryptedDir extends AbstractEncryptedNode {
@Override
protected void determineProperties() {
final Path path = ResourcePathUtils.getPhysicalPath(this);
final Path path = locator.getEncryptedDirectoryPath();
properties.add(new ResourceType(ResourceType.COLLECTION));
properties.add(new DefaultDavProperty<Integer>(DavPropertyName.ISCOLLECTION, 1));
if (Files.exists(path)) {
@@ -161,31 +248,4 @@ class EncryptedDir extends AbstractEncryptedNode {
}
}
/**
* Deletes all files and folders, it visits.
*/
private static class DeletingFileVisitor extends SimpleFileVisitor<Path> {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) throws IOException {
if (attributes.isRegularFile()) {
Files.delete(file);
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
LOG.error("Failed to delete file " + file.toString(), exc);
return FileVisitResult.TERMINATE;
}
}
}
@@ -11,16 +11,17 @@ package org.cryptomator.webdav.jackrabbit;
import java.io.EOFException;
import java.io.IOException;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributes;
import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.DavResource;
import org.apache.jackrabbit.webdav.DavResourceFactory;
import org.apache.jackrabbit.webdav.DavResourceIterator;
import org.apache.jackrabbit.webdav.DavResourceLocator;
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;
@@ -42,11 +43,16 @@ class EncryptedFile extends AbstractEncryptedNode {
protected final CryptoWarningHandler cryptoWarningHandler;
public EncryptedFile(DavResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor, CryptoWarningHandler cryptoWarningHandler) {
public EncryptedFile(CryptoResourceFactory factory, CryptoLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor, CryptoWarningHandler cryptoWarningHandler) {
super(factory, locator, session, lockManager, cryptor);
this.cryptoWarningHandler = cryptoWarningHandler;
}
@Override
protected Path getPhysicalPath() {
return locator.getEncryptedFilePath();
}
@Override
public boolean isCollection() {
return false;
@@ -69,7 +75,7 @@ class EncryptedFile extends AbstractEncryptedNode {
@Override
public void spool(OutputContext outputContext) throws IOException {
final Path path = ResourcePathUtils.getPhysicalPath(this);
final Path path = locator.getEncryptedFilePath();
if (Files.isRegularFile(path)) {
outputContext.setModificationTime(Files.getLastModifiedTime(path).toMillis());
outputContext.setProperty(HttpHeader.ACCEPT_RANGES.asString(), HttpHeaderValue.BYTES.asString());
@@ -93,7 +99,7 @@ class EncryptedFile extends AbstractEncryptedNode {
@Override
protected void determineProperties() {
final Path path = ResourcePathUtils.getPhysicalPath(this);
final Path path = locator.getEncryptedFilePath();
if (Files.exists(path)) {
try (final SeekableByteChannel channel = Files.newByteChannel(path, StandardOpenOption.READ)) {
final Long contentLength = cryptor.decryptedContentLength(channel);
@@ -115,4 +121,40 @@ class EncryptedFile extends AbstractEncryptedNode {
}
}
@Override
public void move(AbstractEncryptedNode dest) throws DavException, IOException {
final Path src = this.locator.getEncryptedFilePath();
final Path dst = dest.locator.getEncryptedFilePath();
// check for conflicts:
if (Files.exists(dst) && Files.getLastModifiedTime(dst).toMillis() > Files.getLastModifiedTime(src).toMillis()) {
throw new DavException(DavServletResponse.SC_CONFLICT, "File at destination already exists: " + dst.toString());
}
// move:
try {
Files.move(src, dst, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
} catch (AtomicMoveNotSupportedException e) {
Files.move(src, dst, StandardCopyOption.REPLACE_EXISTING);
}
}
@Override
public void copy(AbstractEncryptedNode dest, boolean shallow) throws DavException, IOException {
final Path src = this.locator.getEncryptedFilePath();
final Path dst = dest.locator.getEncryptedFilePath();
// check for conflicts:
if (Files.exists(dst) && Files.getLastModifiedTime(dst).toMillis() > Files.getLastModifiedTime(src).toMillis()) {
throw new DavException(DavServletResponse.SC_CONFLICT, "File at destination already exists: " + dst.toString());
}
// copy:
try {
Files.copy(src, dst, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
} catch (AtomicMoveNotSupportedException e) {
Files.copy(src, dst, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING);
}
}
}
@@ -16,7 +16,6 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.jackrabbit.webdav.DavResourceFactory;
import org.apache.jackrabbit.webdav.DavResourceLocator;
import org.apache.jackrabbit.webdav.DavServletRequest;
import org.apache.jackrabbit.webdav.DavSession;
@@ -56,7 +55,7 @@ class EncryptedFilePart extends EncryptedFile {
private final Set<Pair<Long, Long>> requestedContentRanges = new HashSet<Pair<Long, Long>>();
public EncryptedFilePart(DavResourceFactory factory, DavResourceLocator locator, DavSession session, DavServletRequest request, LockManager lockManager, Cryptor cryptor, CryptoWarningHandler cryptoWarningHandler,
public EncryptedFilePart(CryptoResourceFactory factory, CryptoLocator locator, DavSession session, DavServletRequest request, LockManager lockManager, Cryptor cryptor, CryptoWarningHandler cryptoWarningHandler,
ExecutorService backgroundTaskExecutor) {
super(factory, locator, session, lockManager, cryptor, cryptoWarningHandler);
final String rangeHeader = request.getHeader(HttpHeader.RANGE.asString());
@@ -126,7 +125,7 @@ class EncryptedFilePart extends EncryptedFile {
@Override
public void spool(OutputContext outputContext) throws IOException {
final Path path = ResourcePathUtils.getPhysicalPath(this);
final Path path = locator.getEncryptedFilePath();
if (Files.isRegularFile(path)) {
outputContext.setModificationTime(Files.getLastModifiedTime(path).toMillis());
try (final SeekableByteChannel channel = Files.newByteChannel(path, StandardOpenOption.READ)) {
@@ -154,9 +153,9 @@ class EncryptedFilePart extends EncryptedFile {
private class MacAuthenticationJob implements Runnable {
private final DavResourceLocator locator;
private final CryptoLocator locator;
public MacAuthenticationJob(final DavResourceLocator locator) {
public MacAuthenticationJob(final CryptoLocator locator) {
if (locator == null) {
throw new IllegalArgumentException("locator must not be null.");
}
@@ -165,7 +164,7 @@ class EncryptedFilePart extends EncryptedFile {
@Override
public void run() {
final Path path = ResourcePathUtils.getPhysicalPath(locator);
final Path path = locator.getEncryptedFilePath();
if (Files.isRegularFile(path) && Files.isReadable(path)) {
try (final SeekableByteChannel channel = Files.newByteChannel(path, StandardOpenOption.READ)) {
final boolean authentic = cryptor.isAuthentic(channel);
@@ -9,12 +9,11 @@
package org.cryptomator.webdav.jackrabbit;
import java.io.IOException;
import java.nio.file.Path;
import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.DavResource;
import org.apache.jackrabbit.webdav.DavResourceFactory;
import org.apache.jackrabbit.webdav.DavResourceIterator;
import org.apache.jackrabbit.webdav.DavResourceLocator;
import org.apache.jackrabbit.webdav.DavSession;
import org.apache.jackrabbit.webdav.io.InputContext;
import org.apache.jackrabbit.webdav.io.OutputContext;
@@ -23,10 +22,15 @@ import org.cryptomator.crypto.Cryptor;
class NonExistingNode extends AbstractEncryptedNode {
public NonExistingNode(DavResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor) {
public NonExistingNode(CryptoResourceFactory factory, CryptoLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor) {
super(factory, locator, session, lockManager, cryptor);
}
@Override
protected Path getPhysicalPath() {
throw new UnsupportedOperationException("Resource doesn't exist.");
}
@Override
public boolean exists() {
return false;
@@ -37,6 +41,11 @@ class NonExistingNode extends AbstractEncryptedNode {
return false;
}
@Override
public long getModificationTime() {
return -1;
}
@Override
public void spool(OutputContext outputContext) throws IOException {
throw new UnsupportedOperationException("Resource doesn't exist.");
@@ -62,4 +71,14 @@ class NonExistingNode extends AbstractEncryptedNode {
// do nothing.
}
@Override
public void move(AbstractEncryptedNode destination) throws DavException {
throw new UnsupportedOperationException("Resource doesn't exist.");
}
@Override
public void copy(AbstractEncryptedNode destination, boolean shallow) throws DavException {
throw new UnsupportedOperationException("Resource doesn't exist.");
}
}
@@ -11,21 +11,18 @@ package org.cryptomator.webdav.jackrabbit;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import org.apache.jackrabbit.webdav.DavResource;
import org.apache.jackrabbit.webdav.DavResourceLocator;
final class ResourcePathUtils {
private ResourcePathUtils() {
throw new IllegalStateException("not instantiable");
}
public static Path getPhysicalPath(DavResource resource) {
return getPhysicalPath(resource.getLocator());
}
public static Path getPhysicalPath(DavResourceLocator locator) {
public static Path getPhysicalFilePath(CryptoLocator locator) {
return FileSystems.getDefault().getPath(locator.getRepositoryPath());
}
public static Path getPhysicalDirectoryPath(CryptoLocator locator) {
return FileSystems.getDefault().getPath(locator.getDirectoryPath());
}
}
@@ -47,8 +47,8 @@ public class WebDavServlet extends AbstractWebdavServlet {
final String fsRoot = config.getInitParameter(CFG_FS_ROOT);
backgroundTaskExecutor = Executors.newCachedThreadPool();
davSessionProvider = new DavSessionProviderImpl();
davLocatorFactory = new DavLocatorFactoryImpl(fsRoot, cryptor);
davResourceFactory = new DavResourceFactoryImpl(cryptor, cryptoWarningHandler, backgroundTaskExecutor);
davLocatorFactory = new CryptoLocatorFactory(fsRoot, cryptor);
davResourceFactory = new CryptoResourceFactory(cryptor, cryptoWarningHandler, backgroundTaskExecutor);
}
@Override