some more destruction...

This commit is contained in:
Sebastian Stenzel 2015-05-14 21:48:02 +02:00
parent 4cf872f916
commit be369b480b
10 changed files with 411 additions and 234 deletions

View File

@ -44,20 +44,19 @@ abstract class AbstractEncryptedNode implements DavResource {
private static final String DAV_COMPLIANCE_CLASSES = "1, 2";
protected final CryptoResourceFactory factory;
protected final CryptoLocator locator;
protected final DavResourceLocator locator;
protected final DavSession session;
protected final LockManager lockManager;
protected final Cryptor cryptor;
protected final DavPropertySet properties;
protected AbstractEncryptedNode(CryptoResourceFactory factory, CryptoLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor) {
protected AbstractEncryptedNode(CryptoResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor) {
this.factory = factory;
this.locator = locator;
this.session = session;
this.lockManager = lockManager;
this.cryptor = cryptor;
this.properties = new DavPropertySet();
this.determineProperties();
}
protected abstract Path getPhysicalPath();
@ -89,7 +88,7 @@ abstract class AbstractEncryptedNode implements DavResource {
}
@Override
public CryptoLocator getLocator() {
public DavResourceLocator getLocator() {
return locator;
}

View File

@ -0,0 +1,112 @@
package org.cryptomator.webdav.jackrabbit;
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.apache.logging.log4j.util.Strings;
public class CleartextLocatorFactory implements DavLocatorFactory {
private final String pathPrefix;
public CleartextLocatorFactory(String pathPrefix) {
this.pathPrefix = pathPrefix;
}
// resourcePath == repositoryPath. No encryption here.
@Override
public DavResourceLocator createResourceLocator(String prefix, String href) {
final String fullPrefix = prefix.endsWith("/") ? prefix : prefix + "/";
final String relativeHref = StringUtils.removeStart(href, fullPrefix);
final String relativeCleartextPath = EncodeUtil.unescape(StringUtils.removeStart(relativeHref, "/"));
return new CleartextLocator(relativeCleartextPath);
}
@Override
public DavResourceLocator createResourceLocator(String prefix, String workspacePath, String resourcePath) {
return new CleartextLocator(resourcePath);
}
@Override
public DavResourceLocator createResourceLocator(String prefix, String workspacePath, String path, boolean isResourcePath) {
return new CleartextLocator(path);
}
private class CleartextLocator implements DavResourceLocator {
private final String relativeCleartextPath;
private CleartextLocator(String relativeCleartextPath) {
this.relativeCleartextPath = FilenameUtils.normalizeNoEndSeparator(relativeCleartextPath, true);
}
@Override
public String getPrefix() {
return pathPrefix;
}
@Override
public String getResourcePath() {
return relativeCleartextPath;
}
@Override
public String getWorkspacePath() {
return null;
}
@Override
public String getWorkspaceName() {
return null;
}
@Override
public boolean isSameWorkspace(DavResourceLocator locator) {
return false;
}
@Override
public boolean isSameWorkspace(String workspaceName) {
return false;
}
@Override
public String getHref(boolean isCollection) {
final String encodedResourcePath = EncodeUtil.escapePath(getResourcePath());
final String fullPrefix = pathPrefix.endsWith("/") ? pathPrefix : pathPrefix + "/";
final String href = fullPrefix.concat(encodedResourcePath);
assert !href.endsWith("/");
if (isCollection) {
return href.concat("/");
} else {
return href;
}
}
@Override
public boolean isRootLocation() {
return Strings.isEmpty(relativeCleartextPath);
}
@Override
public DavLocatorFactory getFactory() {
return CleartextLocatorFactory.this;
}
@Override
public String getRepositoryPath() {
return relativeCleartextPath;
}
@Override
public String toString() {
return "Locator: " + relativeCleartextPath + " (Prefix: " + pathPrefix + ")";
}
}
}

View File

@ -1,13 +1,19 @@
package org.cryptomator.webdav.jackrabbit;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.io.FilenameUtils;
import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.DavMethods;
import org.apache.jackrabbit.webdav.DavResource;
@ -18,99 +24,168 @@ 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.apache.logging.log4j.util.Strings;
import org.cryptomator.crypto.Cryptor;
import org.cryptomator.webdav.exceptions.IORuntimeException;
import org.cryptomator.crypto.CryptorMetadataSupport;
import org.eclipse.jetty.http.HttpHeader;
public class CryptoResourceFactory implements DavResourceFactory {
public class CryptoResourceFactory implements DavResourceFactory, CryptorMetadataSupport {
private final LockManager lockManager = new SimpleLockManager();
private final Cryptor cryptor;
private final CryptoWarningHandler cryptoWarningHandler;
private final ExecutorService backgroundTaskExecutor;
private final Path dataRoot;
private final Path metadataRoot;
CryptoResourceFactory(Cryptor cryptor, CryptoWarningHandler cryptoWarningHandler, ExecutorService backgroundTaskExecutor) {
CryptoResourceFactory(Cryptor cryptor, CryptoWarningHandler cryptoWarningHandler, ExecutorService backgroundTaskExecutor, String fsRoot) {
this.cryptor = cryptor;
this.cryptoWarningHandler = cryptoWarningHandler;
this.backgroundTaskExecutor = backgroundTaskExecutor;
this.dataRoot = FileSystems.getDefault().getPath(fsRoot).resolve("d");
this.metadataRoot = FileSystems.getDefault().getPath(fsRoot).resolve("m");
}
@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());
if (DavMethods.METHOD_MKCOL.equals(request.getMethod()) || locator.isRootLocation()) {
final Path dirpath = getEncryptedDirectoryPath(locator.getResourcePath());
return createDirectory(locator, request.getDavSession(), dirpath);
}
}
@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());
Path dirpath = null;
try {
dirpath = FileSystems.getDefault().getPath(locator.getDirectoryPath(DavMethods.METHOD_MKCOL.equals(request.getMethod())));
} catch (FileNotFoundException e) {
// no-op
} catch (IOException e) {
throw new IORuntimeException(e);
}
final Path filepath = getEncryptedFilePath(locator.getResourcePath());
final String rangeHeader = request.getHeader(HttpHeader.RANGE.asString());
if (Files.isDirectory(dirpath)) {
return createDirectory(locator, request.getDavSession());
if (filepath.getFileName().toString().endsWith(".dir")) {
final Path dirpath = getEncryptedDirectoryPath(locator.getResourcePath());
return createDirectory(locator, request.getDavSession(), dirpath);
} 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);
return createFilePart(locator, request.getDavSession(), request, filepath);
} else if (Files.isRegularFile(filepath) || DavMethods.METHOD_PUT.equals(request.getMethod())) {
return createFile(locator, request.getDavSession());
return createFile(locator, request.getDavSession(), filepath);
} else {
return createNonExisting(locator, request.getDavSession());
}
}
private DavResource createResource(CryptoLocator locator, DavSession session) throws DavException {
final Path filepath = FileSystems.getDefault().getPath(locator.getRepositoryPath());
Path dirpath = null;
try {
dirpath = FileSystems.getDefault().getPath(locator.getDirectoryPath(false));
} catch (FileNotFoundException e) {
// no-op
} catch (IOException e) {
throw new IORuntimeException(e);
@Override
public final DavResource createResource(DavResourceLocator locator, DavSession session) throws DavException {
if (locator.isRootLocation()) {
final Path dirpath = getEncryptedDirectoryPath(locator.getResourcePath());
return createDirectory(locator, session, dirpath);
}
if (Files.isDirectory(dirpath)) {
return createDirectory(locator, session);
final Path filepath = getEncryptedFilePath(locator.getResourcePath());
if (filepath.getFileName().toString().endsWith(".dir")) {
final Path dirpath = getEncryptedDirectoryPath(locator.getResourcePath());
return createDirectory(locator, session, dirpath);
} else if (Files.isRegularFile(filepath)) {
return createFile(locator, session);
return createFile(locator, session, filepath);
} 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);
/**
* @return Absolute file path for a given cleartext file resourcePath.
* @throws IOException
*/
Path getEncryptedFilePath(String relativeCleartextPath) throws DavException {
final String parentCleartextPath = FilenameUtils.getPathNoEndSeparator(relativeCleartextPath);
final Path parent = getEncryptedDirectoryPath(parentCleartextPath);
final String cleartextFilename = FilenameUtils.getName(relativeCleartextPath);
try {
final String encryptedFilename = cryptor.encryptFilename(cleartextFilename, this);
return parent.resolve(encryptedFilename);
} catch (IOException e) {
throw new DavException(DavServletResponse.SC_INTERNAL_SERVER_ERROR, e);
}
}
private EncryptedFile createFile(CryptoLocator locator, DavSession session) {
return new EncryptedFile(this, locator, session, lockManager, cryptor, cryptoWarningHandler);
/**
* @return Absolute directory path for a given cleartext directory resourcePath.
* @throws IOException
*/
Path getEncryptedDirectoryPath(String relativeCleartextPath) throws DavException {
assert Strings.isEmpty(relativeCleartextPath) || !relativeCleartextPath.endsWith("/");
try {
final Path result;
if (Strings.isEmpty(relativeCleartextPath)) {
// root level
final String fixedRootDirectory = cryptor.encryptDirectoryPath("", FileSystems.getDefault().getSeparator());
result = dataRoot.resolve(fixedRootDirectory);
} else {
final String parentCleartextPath = FilenameUtils.getPathNoEndSeparator(relativeCleartextPath);
final Path parent = getEncryptedDirectoryPath(parentCleartextPath);
final String cleartextFilename = FilenameUtils.getName(relativeCleartextPath);
final String encryptedFilename = cryptor.encryptFilename(cleartextFilename, CryptoResourceFactory.this);
final Path directoryFile = parent.resolve(encryptedFilename);
final String directoryId;
if (Files.exists(directoryFile)) {
directoryId = new String(readAllBytesAtomically(directoryFile), StandardCharsets.UTF_8);
} else {
directoryId = UUID.randomUUID().toString();
writeAllBytesAtomically(directoryFile, directoryId.getBytes(StandardCharsets.UTF_8));
}
final String directory = cryptor.encryptDirectoryPath(directoryId, FileSystems.getDefault().getSeparator());
result = dataRoot.resolve(directory);
}
Files.createDirectories(result);
return result;
} catch (IOException e) {
throw new DavException(DavServletResponse.SC_INTERNAL_SERVER_ERROR, e);
}
}
private EncryptedDir createDirectory(CryptoLocator locator, DavSession session) {
return new EncryptedDir(this, locator, session, lockManager, cryptor);
private EncryptedFile createFilePart(DavResourceLocator locator, DavSession session, DavServletRequest request, Path filePath) {
return new EncryptedFilePart(this, locator, session, request, lockManager, cryptor, cryptoWarningHandler, backgroundTaskExecutor, filePath);
}
private NonExistingNode createNonExisting(CryptoLocator locator, DavSession session) {
private EncryptedFile createFile(DavResourceLocator locator, DavSession session, Path filePath) {
return new EncryptedFile(this, locator, session, lockManager, cryptor, cryptoWarningHandler, filePath);
}
private EncryptedDir createDirectory(DavResourceLocator locator, DavSession session, Path dirPath) {
return new EncryptedDir(this, locator, session, lockManager, cryptor, dirPath);
}
private NonExistingNode createNonExisting(DavResourceLocator locator, DavSession session) {
return new NonExistingNode(this, locator, session, lockManager, cryptor);
}
/* IO support */
private void writeAllBytesAtomically(Path path, byte[] bytes) throws IOException {
try (final FileChannel c = FileChannel.open(path, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.DSYNC); final FileLock lock = c.lock()) {
c.write(ByteBuffer.wrap(bytes));
}
}
private byte[] readAllBytesAtomically(Path path) throws IOException {
try (final FileChannel c = FileChannel.open(path, StandardOpenOption.READ, StandardOpenOption.DSYNC); final FileLock lock = c.lock(0L, Long.MAX_VALUE, true)) {
final ByteBuffer buffer = ByteBuffer.allocate((int) c.size());
c.read(buffer);
return buffer.array();
}
}
@Override
public void writeMetadata(String metadataGroup, byte[] encryptedMetadata) throws IOException {
final Path metadataDir = metadataRoot.resolve(metadataGroup.substring(0, 2));
Files.createDirectories(metadataDir);
final Path metadataFile = metadataDir.resolve(metadataGroup.substring(2));
writeAllBytesAtomically(metadataFile, encryptedMetadata);
}
@Override
public byte[] readMetadata(String metadataGroup) throws IOException {
final Path metadataDir = metadataRoot.resolve(metadataGroup.substring(0, 2));
final Path metadataFile = metadataDir.resolve(metadataGroup.substring(2));
if (!Files.isReadable(metadataFile)) {
return null;
} else {
return readAllBytesAtomically(metadataFile);
}
}
}

View File

@ -11,16 +11,16 @@ package org.cryptomator.webdav.jackrabbit;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.DirectoryStream;
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 java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.DavResource;
@ -37,9 +37,9 @@ import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
import org.apache.jackrabbit.webdav.property.ResourceType;
import org.cryptomator.crypto.Cryptor;
import org.cryptomator.crypto.exceptions.CounterOverflowException;
import org.cryptomator.crypto.exceptions.DecryptFailedException;
import org.cryptomator.crypto.exceptions.EncryptFailedException;
import org.cryptomator.webdav.exceptions.DavRuntimeException;
import org.cryptomator.webdav.exceptions.DecryptFailedRuntimeException;
import org.cryptomator.webdav.exceptions.IORuntimeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -47,18 +47,20 @@ import org.slf4j.LoggerFactory;
class EncryptedDir extends AbstractEncryptedNode {
private static final Logger LOG = LoggerFactory.getLogger(EncryptedDir.class);
private final Path directoryPath;
public EncryptedDir(CryptoResourceFactory factory, CryptoLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor) {
public EncryptedDir(CryptoResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor, Path directoryPath) {
super(factory, locator, session, lockManager, cryptor);
if (directoryPath == null || !Files.isDirectory(directoryPath)) {
throw new IllegalArgumentException("directoryPath must be an existing directory, but was " + directoryPath);
}
this.directoryPath = directoryPath;
determineProperties();
}
@Override
protected Path getPhysicalPath() {
try {
return locator.getEncryptedDirectoryPath(false);
} catch (IOException e) {
throw new IORuntimeException(e);
}
return directoryPath;
}
@Override
@ -68,17 +70,14 @@ class EncryptedDir extends AbstractEncryptedNode {
@Override
public boolean exists() {
try {
return Files.isDirectory(locator.getEncryptedDirectoryPath(false));
} catch (IOException e) {
return false;
}
assert Files.isDirectory(directoryPath);
return true;
}
@Override
public long getModificationTime() {
try {
return Files.getLastModifiedTime(locator.getEncryptedDirectoryPath(false)).toMillis();
return Files.getLastModifiedTime(directoryPath).toMillis();
} catch (IOException e) {
return -1;
}
@ -101,19 +100,18 @@ class EncryptedDir extends AbstractEncryptedNode {
}
}
private void addMemberDir(CryptoLocator childLocator, InputContext inputContext) throws DavException {
private void addMemberDir(DavResourceLocator childLocator, InputContext inputContext) throws DavException {
try {
Files.createDirectories(childLocator.getEncryptedDirectoryPath(true));
// the following invokation will create nonexisting directories:
factory.getEncryptedDirectoryPath(childLocator.getResourcePath());
} catch (SecurityException e) {
throw new DavException(DavServletResponse.SC_FORBIDDEN, e);
} catch (IOException e) {
LOG.error("Failed to create subdirectory.", e);
throw new IORuntimeException(e);
}
}
private void addMemberFile(CryptoLocator childLocator, InputContext inputContext) throws DavException {
try (final SeekableByteChannel channel = Files.newByteChannel(childLocator.getEncryptedFilePath(), StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
private void addMemberFile(DavResourceLocator childLocator, InputContext inputContext) throws DavException {
final Path filePath = factory.getEncryptedFilePath(childLocator.getResourcePath());
try (final SeekableByteChannel channel = Files.newByteChannel(filePath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
cryptor.encryptFile(inputContext.getInputStream(), channel);
} catch (SecurityException e) {
throw new DavException(DavServletResponse.SC_FORBIDDEN, e);
@ -134,17 +132,17 @@ class EncryptedDir extends AbstractEncryptedNode {
@Override
public DavResourceIterator getMembers() {
try {
final DirectoryStream<Path> directoryStream = Files.newDirectoryStream(locator.getEncryptedDirectoryPath(false), cryptor.getPayloadFilesFilter());
final DirectoryStream<Path> directoryStream = Files.newDirectoryStream(directoryPath, cryptor.getPayloadFilesFilter());
final List<DavResource> result = new ArrayList<>();
for (final Path childPath : directoryStream) {
try {
final DavResourceLocator childLocator = locator.getFactory().createSubresourceLocator(locator, childPath.getFileName().toString());
// final DavResourceLocator childLocator = locator.getFactory().createResourceLocator(locator.getPrefix(),
// locator.getWorkspacePath(), childPath.toString(), false);
final String cleartextFilename = cryptor.decryptFilename(childPath.getFileName().toString(), factory);
final String cleartextFilepath = FilenameUtils.concat(getResourcePath(), cleartextFilename);
final DavResourceLocator childLocator = locator.getFactory().createResourceLocator(locator.getPrefix(), locator.getWorkspacePath(), cleartextFilepath);
final DavResource resource = factory.createResource(childLocator, session);
result.add(resource);
} catch (DecryptFailedRuntimeException e) {
} catch (DecryptFailedException e) {
LOG.warn("Decryption of resource failed: " + childPath);
continue;
}
@ -160,7 +158,7 @@ class EncryptedDir extends AbstractEncryptedNode {
}
@Override
public void removeMember(DavResource member) {
public void removeMember(DavResource member) throws DavException {
if (member instanceof AbstractEncryptedNode) {
removeMember((AbstractEncryptedNode) member);
} else {
@ -168,13 +166,19 @@ class EncryptedDir extends AbstractEncryptedNode {
}
}
private void removeMember(AbstractEncryptedNode member) {
private void removeMember(AbstractEncryptedNode member) throws DavException {
try {
Files.deleteIfExists(member.getLocator().getEncryptedFilePath());
if (member.isCollection()) {
member.getMembers().forEachRemaining(m -> securelyRemoveMemberOfCollection(member, m));
Files.deleteIfExists(member.getLocator().getEncryptedDirectoryPath(false));
// remove sub-members recursively before deleting own directory
for (Iterator<DavResource> iterator = member.getMembers(); iterator.hasNext();) {
DavResource m = iterator.next();
member.removeMember(m);
}
final Path memberDirectoryPath = factory.getEncryptedDirectoryPath(member.getResourcePath());
Files.deleteIfExists(memberDirectoryPath);
}
final Path memberPath = factory.getEncryptedFilePath(member.getResourcePath());
Files.deleteIfExists(memberPath);
} catch (FileNotFoundException e) {
// no-op
} catch (IOException e) {
@ -182,58 +186,52 @@ class EncryptedDir extends AbstractEncryptedNode {
}
}
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(false);
final Path dstDir = dest.locator.getEncryptedDirectoryPath(true);
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);
}
throw new UnsupportedOperationException("not yet implemented");
// final Path srcDir = this.locator.getEncryptedDirectoryPath(false);
// final Path dstDir = dest.locator.getEncryptedDirectoryPath(true);
// 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(false);
final Path dstDir = dest.locator.getEncryptedDirectoryPath(true);
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);
}
throw new UnsupportedOperationException("not yet implemented");
// final Path srcDir = this.locator.getEncryptedDirectoryPath(false);
// final Path dstDir = dest.locator.getEncryptedDirectoryPath(true);
// 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
@ -243,23 +241,15 @@ class EncryptedDir extends AbstractEncryptedNode {
@Override
protected void determineProperties() {
Path path;
try {
path = locator.getEncryptedDirectoryPath(false);
} catch (IOException e) {
throw new IORuntimeException(e);
}
properties.add(new ResourceType(ResourceType.COLLECTION));
properties.add(new DefaultDavProperty<Integer>(DavPropertyName.ISCOLLECTION, 1));
if (Files.exists(path)) {
try {
final BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
properties.add(new DefaultDavProperty<String>(DavPropertyName.CREATIONDATE, FileTimeUtils.toRfc1123String(attrs.creationTime())));
properties.add(new DefaultDavProperty<String>(DavPropertyName.GETLASTMODIFIED, FileTimeUtils.toRfc1123String(attrs.lastModifiedTime())));
} catch (IOException e) {
LOG.error("Error determining metadata " + path.toString(), e);
// don't add any further properties
}
try {
final BasicFileAttributes attrs = Files.readAttributes(directoryPath, BasicFileAttributes.class);
properties.add(new DefaultDavProperty<String>(DavPropertyName.CREATIONDATE, FileTimeUtils.toRfc1123String(attrs.creationTime())));
properties.add(new DefaultDavProperty<String>(DavPropertyName.GETLASTMODIFIED, FileTimeUtils.toRfc1123String(attrs.lastModifiedTime())));
} catch (IOException e) {
LOG.error("Error determining metadata " + directoryPath.toString(), e);
// don't add any further properties
}
}

View File

@ -11,17 +11,15 @@ 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.DavResourceIterator;
import org.apache.jackrabbit.webdav.DavServletResponse;
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;
@ -42,15 +40,21 @@ class EncryptedFile extends AbstractEncryptedNode {
private static final Logger LOG = LoggerFactory.getLogger(EncryptedFile.class);
protected final CryptoWarningHandler cryptoWarningHandler;
protected final Path filePath;
public EncryptedFile(CryptoResourceFactory factory, CryptoLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor, CryptoWarningHandler cryptoWarningHandler) {
public EncryptedFile(CryptoResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor, CryptoWarningHandler cryptoWarningHandler, Path filePath) {
super(factory, locator, session, lockManager, cryptor);
if (filePath == null) {
throw new IllegalArgumentException("filePath must not be null");
}
this.cryptoWarningHandler = cryptoWarningHandler;
this.filePath = filePath;
this.determineProperties();
}
@Override
protected Path getPhysicalPath() {
return locator.getEncryptedFilePath();
return filePath;
}
@Override
@ -75,11 +79,10 @@ class EncryptedFile extends AbstractEncryptedNode {
@Override
public void spool(OutputContext outputContext) throws IOException {
final Path path = locator.getEncryptedFilePath();
if (Files.isRegularFile(path)) {
outputContext.setModificationTime(Files.getLastModifiedTime(path).toMillis());
if (Files.isRegularFile(filePath)) {
outputContext.setModificationTime(Files.getLastModifiedTime(filePath).toMillis());
outputContext.setProperty(HttpHeader.ACCEPT_RANGES.asString(), HttpHeaderValue.BYTES.asString());
try (final SeekableByteChannel channel = Files.newByteChannel(path, StandardOpenOption.READ)) {
try (final SeekableByteChannel channel = Files.newByteChannel(filePath, StandardOpenOption.READ)) {
final Long contentLength = cryptor.decryptedContentLength(channel);
if (contentLength != null) {
outputContext.setContentLength(contentLength);
@ -92,20 +95,19 @@ class EncryptedFile extends AbstractEncryptedNode {
} catch (MacAuthenticationFailedException e) {
cryptoWarningHandler.macAuthFailed(getLocator().getResourcePath());
} catch (DecryptFailedException e) {
throw new IOException("Error decrypting file " + path.toString(), e);
throw new IOException("Error decrypting file " + filePath.toString(), e);
}
}
}
@Override
protected void determineProperties() {
final Path path = locator.getEncryptedFilePath();
if (Files.exists(path)) {
try (final SeekableByteChannel channel = Files.newByteChannel(path, StandardOpenOption.READ)) {
if (Files.isRegularFile(filePath)) {
try (final SeekableByteChannel channel = Files.newByteChannel(filePath, StandardOpenOption.READ)) {
final Long contentLength = cryptor.decryptedContentLength(channel);
properties.add(new DefaultDavProperty<Long>(DavPropertyName.GETCONTENTLENGTH, contentLength));
} catch (IOException e) {
LOG.error("Error reading filesize " + path.toString(), e);
LOG.error("Error reading filesize " + filePath.toString(), e);
throw new IORuntimeException(e);
} catch (MacAuthenticationFailedException e) {
LOG.warn("Content length couldn't be determined due to MAC authentication violation.");
@ -113,12 +115,12 @@ class EncryptedFile extends AbstractEncryptedNode {
}
try {
final BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
final BasicFileAttributes attrs = Files.readAttributes(filePath, BasicFileAttributes.class);
properties.add(new DefaultDavProperty<String>(DavPropertyName.CREATIONDATE, FileTimeUtils.toRfc1123String(attrs.creationTime())));
properties.add(new DefaultDavProperty<String>(DavPropertyName.GETLASTMODIFIED, FileTimeUtils.toRfc1123String(attrs.lastModifiedTime())));
properties.add(new HttpHeaderProperty(HttpHeader.ACCEPT_RANGES.asString(), HttpHeaderValue.BYTES.asString()));
} catch (IOException e) {
LOG.error("Error determining metadata " + path.toString(), e);
LOG.error("Error determining metadata " + filePath.toString(), e);
throw new IORuntimeException(e);
}
}
@ -126,38 +128,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);
}
throw new UnsupportedOperationException("not yet implemented");
// 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);
}
throw new UnsupportedOperationException("not yet implemented");
// 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);
// }
}
}

View File

@ -55,9 +55,9 @@ class EncryptedFilePart extends EncryptedFile {
private final Set<Pair<Long, Long>> requestedContentRanges = new HashSet<Pair<Long, Long>>();
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);
public EncryptedFilePart(CryptoResourceFactory factory, DavResourceLocator locator, DavSession session, DavServletRequest request, LockManager lockManager, Cryptor cryptor, CryptoWarningHandler cryptoWarningHandler,
ExecutorService backgroundTaskExecutor, Path filePath) {
super(factory, locator, session, lockManager, cryptor, cryptoWarningHandler, filePath);
final String rangeHeader = request.getHeader(HttpHeader.RANGE.asString());
if (rangeHeader == null) {
throw new IllegalArgumentException("HTTP request doesn't contain a range header");
@ -125,25 +125,23 @@ class EncryptedFilePart extends EncryptedFile {
@Override
public void spool(OutputContext outputContext) throws IOException {
final Path path = locator.getEncryptedFilePath();
if (Files.isRegularFile(path)) {
outputContext.setModificationTime(Files.getLastModifiedTime(path).toMillis());
try (final SeekableByteChannel channel = Files.newByteChannel(path, StandardOpenOption.READ)) {
final Long fileSize = cryptor.decryptedContentLength(channel);
final Pair<Long, Long> range = getUnionRange(fileSize);
final Long rangeLength = range.getRight() - range.getLeft() + 1;
outputContext.setContentLength(rangeLength);
outputContext.setProperty(HttpHeader.CONTENT_RANGE.asString(), getContentRangeHeader(range.getLeft(), range.getRight(), fileSize));
if (outputContext.hasStream()) {
cryptor.decryptRange(channel, outputContext.getOutputStream(), range.getLeft(), rangeLength);
}
} catch (EOFException e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Unexpected end of stream during delivery of partial content (client hung up).");
}
} catch (DecryptFailedException e) {
throw new IOException("Error decrypting file " + path.toString(), e);
assert Files.isRegularFile(filePath);
outputContext.setModificationTime(Files.getLastModifiedTime(filePath).toMillis());
try (final SeekableByteChannel channel = Files.newByteChannel(filePath, StandardOpenOption.READ)) {
final Long fileSize = cryptor.decryptedContentLength(channel);
final Pair<Long, Long> range = getUnionRange(fileSize);
final Long rangeLength = range.getRight() - range.getLeft() + 1;
outputContext.setContentLength(rangeLength);
outputContext.setProperty(HttpHeader.CONTENT_RANGE.asString(), getContentRangeHeader(range.getLeft(), range.getRight(), fileSize));
if (outputContext.hasStream()) {
cryptor.decryptRange(channel, outputContext.getOutputStream(), range.getLeft(), rangeLength);
}
} catch (EOFException e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Unexpected end of stream during delivery of partial content (client hung up).");
}
} catch (DecryptFailedException e) {
throw new IOException("Error decrypting file " + filePath.toString(), e);
}
}
@ -153,9 +151,9 @@ class EncryptedFilePart extends EncryptedFile {
private class MacAuthenticationJob implements Runnable {
private final CryptoLocator locator;
private final DavResourceLocator locator;
public MacAuthenticationJob(final CryptoLocator locator) {
public MacAuthenticationJob(final DavResourceLocator locator) {
if (locator == null) {
throw new IllegalArgumentException("locator must not be null.");
}
@ -164,18 +162,16 @@ class EncryptedFilePart extends EncryptedFile {
@Override
public void run() {
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);
if (!authentic) {
cryptoWarningHandler.macAuthFailed(locator.getResourcePath());
}
} catch (ClosedByInterruptException ex) {
LOG.debug("Couldn't finish MAC verification due to interruption of worker thread.");
} catch (IOException e) {
LOG.error("IOException during MAC verification of " + path.toString(), e);
assert Files.isRegularFile(filePath);
try (final SeekableByteChannel channel = Files.newByteChannel(filePath, StandardOpenOption.READ)) {
final boolean authentic = cryptor.isAuthentic(channel);
if (!authentic) {
cryptoWarningHandler.macAuthFailed(locator.getResourcePath());
}
} catch (ClosedByInterruptException ex) {
LOG.debug("Couldn't finish MAC verification due to interruption of worker thread.");
} catch (IOException e) {
LOG.error("IOException during MAC verification of " + filePath.toString(), e);
}
}

View File

@ -14,6 +14,7 @@ import java.nio.file.Path;
import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.DavResource;
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;
@ -22,7 +23,7 @@ import org.cryptomator.crypto.Cryptor;
class NonExistingNode extends AbstractEncryptedNode {
public NonExistingNode(CryptoResourceFactory factory, CryptoLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor) {
public NonExistingNode(CryptoResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor) {
super(factory, locator, session, lockManager, cryptor);
}

View File

@ -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 CryptoLocatorFactory(fsRoot, cryptor);
davResourceFactory = new CryptoResourceFactory(cryptor, cryptoWarningHandler, backgroundTaskExecutor);
davLocatorFactory = new CleartextLocatorFactory(config.getServletContext().getContextPath()); // CryptoLocatorFactory(fsRoot, cryptor);
davResourceFactory = new CryptoResourceFactory(cryptor, cryptoWarningHandler, backgroundTaskExecutor, fsRoot);
}
@Override

View File

@ -288,8 +288,8 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
}
@Override
public String encryptDirectoryPath(String cleartextPath, String nativePathSep) {
final byte[] cleartextBytes = cleartextPath.getBytes(StandardCharsets.UTF_8);
public String encryptDirectoryPath(String cleartextDirectoryId, String nativePathSep) {
final byte[] cleartextBytes = cleartextDirectoryId.getBytes(StandardCharsets.UTF_8);
byte[] encryptedBytes = AesSivCipherUtil.sivEncrypt(primaryMasterKey, hMacMasterKey, cleartextBytes);
final byte[] hashed = sha256().digest(encryptedBytes);
final String encryptedThenHashedPath = ENCRYPTED_FILENAME_CODEC.encodeAsString(hashed);

View File

@ -47,11 +47,11 @@ public interface Cryptor extends Destroyable {
/**
* Encrypts a given plaintext path representing a directory structure. See {@link #encryptFilename(String, CryptorMetadataSupport)} for contents inside directories.
*
* @param cleartextPath A relative path (UTF-8 encoded), whose path components are separated by '/'
* @param cleartextDirectoryId A relative path (UTF-8 encoded), whose path components are separated by '/'
* @param nativePathSep Path separator like "/" used on local file system. Must not be null, even if cleartextPath is a sole file name without any path separators.
* @return Encrypted path.
*/
String encryptDirectoryPath(String cleartextPath, String nativePathSep);
String encryptDirectoryPath(String cleartextDirectoryId, String nativePathSep);
/**
* Encrypts the name of a file. See {@link #encryptDirectoryPath(String, char)} for parent dir.