mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-09-20 06:54:16 +00:00
refactored directory structure, so windows (and OneDrive) can handle vaults better
This commit is contained in:
@@ -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
|
||||
|
||||
+30
-45
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user