mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-28 07:30:19 +00:00
several refactorings, especially concerning LOCK operations on windows
This commit is contained in:
@@ -19,6 +19,7 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.jackrabbit.webdav.DavException;
|
||||
import org.apache.jackrabbit.webdav.DavResource;
|
||||
import org.apache.jackrabbit.webdav.DavResourceLocator;
|
||||
@@ -196,7 +197,7 @@ abstract class AbstractEncryptedNode implements DavResource {
|
||||
return null;
|
||||
}
|
||||
|
||||
final String parentResource = FilenameUtils.getPathNoEndSeparator(locator.getResourcePath());
|
||||
final String parentResource = StringUtils.prependIfMissing(FilenameUtils.getPathNoEndSeparator(locator.getResourcePath()), "/");
|
||||
final DavResourceLocator parentLocator = locator.getFactory().createResourceLocator(locator.getPrefix(), locator.getWorkspacePath(), parentResource);
|
||||
try {
|
||||
return getFactory().createResource(parentLocator, session);
|
||||
@@ -255,7 +256,11 @@ abstract class AbstractEncryptedNode implements DavResource {
|
||||
@Override
|
||||
public ActiveLock[] getLocks() {
|
||||
final ActiveLock exclusiveWriteLock = getLock(Type.WRITE, Scope.EXCLUSIVE);
|
||||
return new ActiveLock[] {exclusiveWriteLock};
|
||||
if (exclusiveWriteLock != null) {
|
||||
return new ActiveLock[] {exclusiveWriteLock};
|
||||
} else {
|
||||
return new ActiveLock[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -12,27 +12,30 @@ public class CleartextLocatorFactory implements DavLocatorFactory {
|
||||
private final String pathPrefix;
|
||||
|
||||
public CleartextLocatorFactory(String pathPrefix) {
|
||||
this.pathPrefix = pathPrefix;
|
||||
this.pathPrefix = StringUtils.removeEnd(pathPrefix, "/");
|
||||
}
|
||||
|
||||
// resourcePath == repositoryPath. No encryption here.
|
||||
|
||||
@Override
|
||||
public DavResourceLocator createResourceLocator(String prefix, String href) {
|
||||
final String fullPrefix = prefix.endsWith("/") ? prefix : prefix + "/";
|
||||
final String fullPrefix = StringUtils.removeEnd(prefix, "/");
|
||||
final String relativeHref = StringUtils.removeStart(href, fullPrefix);
|
||||
|
||||
final String relativeCleartextPath = EncodeUtil.unescape(StringUtils.removeStart(relativeHref, "/"));
|
||||
final String relativeCleartextPath = EncodeUtil.unescape(relativeHref);
|
||||
assert relativeCleartextPath.startsWith("/");
|
||||
return new CleartextLocator(relativeCleartextPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DavResourceLocator createResourceLocator(String prefix, String workspacePath, String resourcePath) {
|
||||
assert resourcePath.startsWith("/");
|
||||
return new CleartextLocator(resourcePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DavResourceLocator createResourceLocator(String prefix, String workspacePath, String path, boolean isResourcePath) {
|
||||
assert path.startsWith("/");
|
||||
return new CleartextLocator(path);
|
||||
}
|
||||
|
||||
@@ -41,7 +44,7 @@ public class CleartextLocatorFactory implements DavLocatorFactory {
|
||||
private final String relativeCleartextPath;
|
||||
|
||||
private CleartextLocator(String relativeCleartextPath) {
|
||||
this.relativeCleartextPath = FilenameUtils.normalizeNoEndSeparator(relativeCleartextPath, true);
|
||||
this.relativeCleartextPath = StringUtils.prependIfMissing(FilenameUtils.normalizeNoEndSeparator(relativeCleartextPath, true), "/");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -76,20 +79,19 @@ public class CleartextLocatorFactory implements DavLocatorFactory {
|
||||
|
||||
@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.equals(fullPrefix) || !href.endsWith("/");
|
||||
if (isCollection) {
|
||||
return href.concat("/");
|
||||
final String encodedResourcePath = EncodeUtil.escapePath(relativeCleartextPath);
|
||||
if (isRootLocation()) {
|
||||
return pathPrefix + "/";
|
||||
} else if (isCollection) {
|
||||
return pathPrefix + encodedResourcePath + "/";
|
||||
} else {
|
||||
return href;
|
||||
return pathPrefix + encodedResourcePath;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRootLocation() {
|
||||
return Strings.isEmpty(relativeCleartextPath);
|
||||
return "/".equals(relativeCleartextPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -181,10 +181,11 @@ public class CryptoResourceFactory implements DavResourceFactory, FileConstants
|
||||
|
||||
/**
|
||||
* @return Absolute file path for a given cleartext file resourcePath.
|
||||
* @throws NonExistingParentException If one ancestor of the enrypted path is missing
|
||||
* @throws NonExistingParentException If one ancestor of the encrypted path is missing
|
||||
*/
|
||||
Path getEncryptedFilePath(String relativeCleartextPath, boolean createNonExisting) throws NonExistingParentException {
|
||||
final String parentCleartextPath = FilenameUtils.getPathNoEndSeparator(relativeCleartextPath);
|
||||
assert relativeCleartextPath.startsWith("/");
|
||||
final String parentCleartextPath = StringUtils.prependIfMissing(FilenameUtils.getPathNoEndSeparator(relativeCleartextPath), "/");
|
||||
final Path parent = getEncryptedDirectoryPath(parentCleartextPath, createNonExisting);
|
||||
final String cleartextFilename = FilenameUtils.getName(relativeCleartextPath);
|
||||
try {
|
||||
@@ -197,10 +198,11 @@ public class CryptoResourceFactory implements DavResourceFactory, FileConstants
|
||||
|
||||
/**
|
||||
* @return Absolute file path for a given cleartext file resourcePath.
|
||||
* @throws NonExistingParentException If one ancestor of the enrypted path is missing
|
||||
* @throws NonExistingParentException If one ancestor of the encrypted path is missing
|
||||
*/
|
||||
Path getEncryptedDirectoryFilePath(String relativeCleartextPath, boolean createNonExisting) throws NonExistingParentException {
|
||||
final String parentCleartextPath = FilenameUtils.getPathNoEndSeparator(relativeCleartextPath);
|
||||
assert relativeCleartextPath.startsWith("/");
|
||||
final String parentCleartextPath = StringUtils.prependIfMissing(FilenameUtils.getPathNoEndSeparator(relativeCleartextPath), "/");
|
||||
final Path parent = getEncryptedDirectoryPath(parentCleartextPath, createNonExisting);
|
||||
final String cleartextFilename = FilenameUtils.getName(relativeCleartextPath);
|
||||
try {
|
||||
@@ -217,15 +219,16 @@ public class CryptoResourceFactory implements DavResourceFactory, FileConstants
|
||||
* @throws NonExistingParentException if one ancestor directory is missing.
|
||||
*/
|
||||
private Path getEncryptedDirectoryPath(String relativeCleartextPath, boolean createNonExisting) throws NonExistingParentException {
|
||||
assert Strings.isEmpty(relativeCleartextPath) || !relativeCleartextPath.endsWith("/");
|
||||
assert relativeCleartextPath.startsWith("/");
|
||||
assert "/".equals(relativeCleartextPath) || !relativeCleartextPath.endsWith("/");
|
||||
try {
|
||||
final Path result;
|
||||
if (Strings.isEmpty(relativeCleartextPath)) {
|
||||
if ("/".equals(relativeCleartextPath)) {
|
||||
// root level
|
||||
final String fixedRootDirectory = cryptor.encryptDirectoryPath("", FileSystems.getDefault().getSeparator());
|
||||
result = dataRoot.resolve(fixedRootDirectory);
|
||||
} else {
|
||||
final String parentCleartextPath = FilenameUtils.getPathNoEndSeparator(relativeCleartextPath);
|
||||
final String parentCleartextPath = StringUtils.prependIfMissing(FilenameUtils.getPathNoEndSeparator(relativeCleartextPath), "/");
|
||||
final Path parent = getEncryptedDirectoryPath(parentCleartextPath, createNonExisting);
|
||||
final String cleartextFilename = FilenameUtils.getName(relativeCleartextPath);
|
||||
final String encryptedFilename = filenameTranslator.getEncryptedDirFileName(cleartextFilename);
|
||||
|
||||
@@ -15,10 +15,14 @@ import java.nio.channels.FileChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.AtomicMoveNotSupportedException;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.FileVisitor;
|
||||
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.nio.file.attribute.FileTime;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
@@ -38,12 +42,12 @@ import org.apache.jackrabbit.webdav.DavServletResponse;
|
||||
import org.apache.jackrabbit.webdav.DavSession;
|
||||
import org.apache.jackrabbit.webdav.io.InputContext;
|
||||
import org.apache.jackrabbit.webdav.io.OutputContext;
|
||||
import org.apache.jackrabbit.webdav.lock.ActiveLock;
|
||||
import org.apache.jackrabbit.webdav.lock.LockManager;
|
||||
import org.apache.jackrabbit.webdav.property.DavPropertyName;
|
||||
import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
|
||||
import org.apache.jackrabbit.webdav.property.ResourceType;
|
||||
import org.cryptomator.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;
|
||||
@@ -92,6 +96,11 @@ class EncryptedDir extends AbstractEncryptedNode implements FileConstants {
|
||||
}
|
||||
return directoryPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return Files.exists(filePath) && Files.exists(getDirectoryPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCollection() {
|
||||
@@ -159,14 +168,10 @@ class EncryptedDir extends AbstractEncryptedNode implements FileConstants {
|
||||
final Path filePath = dirPath.resolve(ciphertextFilename);
|
||||
final Path tmpFilePath = Files.createTempFile(dirPath, null, null);
|
||||
// encrypt to tmp file:
|
||||
try (final FileChannel c = FileChannel.open(tmpFilePath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
final SilentlyFailingFileLock lock = new SilentlyFailingFileLock(c, false)) {
|
||||
cryptor.encryptFile(inputContext.getInputStream(), c);
|
||||
try (final FileChannel c = FileChannel.open(tmpFilePath, StandardOpenOption.WRITE, StandardOpenOption.DSYNC)) {
|
||||
long asd = cryptor.encryptFile(inputContext.getInputStream(), c);
|
||||
} catch (SecurityException e) {
|
||||
throw new DavException(DavServletResponse.SC_FORBIDDEN, e);
|
||||
} catch (CounterOverflowException e) {
|
||||
// lets indicate this to the client as a "file too big" error
|
||||
throw new DavException(DavServletResponse.SC_INSUFFICIENT_SPACE_ON_RESOURCE, e);
|
||||
} catch (EncryptFailedException e) {
|
||||
LOG.error("Encryption failed for unknown reasons.", e);
|
||||
throw new IllegalStateException("Encryption failed for unknown reasons.", e);
|
||||
@@ -188,18 +193,17 @@ class EncryptedDir extends AbstractEncryptedNode implements FileConstants {
|
||||
|
||||
@Override
|
||||
public DavResourceIterator getMembers() {
|
||||
try {
|
||||
final Path dirPath = getDirectoryPath();
|
||||
if (dirPath == null) {
|
||||
throw new DavException(DavServletResponse.SC_NOT_FOUND);
|
||||
}
|
||||
final DirectoryStream<Path> directoryStream = Files.newDirectoryStream(dirPath, DIRECTORY_CONTENT_FILTER);
|
||||
final Path dirPath = getDirectoryPath();
|
||||
if (dirPath == null) {
|
||||
throw new DavRuntimeException(new DavException(DavServletResponse.SC_NOT_FOUND));
|
||||
}
|
||||
|
||||
try (final DirectoryStream<Path> directoryStream = Files.newDirectoryStream(dirPath, DIRECTORY_CONTENT_FILTER)) {
|
||||
final List<DavResource> result = new ArrayList<>();
|
||||
|
||||
for (final Path childPath : directoryStream) {
|
||||
try {
|
||||
final String cleartextFilename = filenameTranslator.getCleartextFilename(childPath.getFileName().toString());
|
||||
final String cleartextFilepath = FilenameUtils.concat(getResourcePath(), cleartextFilename);
|
||||
final String cleartextFilepath = locator.isRootLocation() ? '/' + cleartextFilename : locator.getResourcePath() + '/' + cleartextFilename;
|
||||
final DavResourceLocator childLocator = locator.getFactory().createResourceLocator(locator.getPrefix(), locator.getWorkspacePath(), cleartextFilepath);
|
||||
final DavResource resource;
|
||||
if (StringUtil.endsWithIgnoreCase(childPath.getFileName().toString(), DIR_EXT)) {
|
||||
@@ -208,7 +212,9 @@ class EncryptedDir extends AbstractEncryptedNode implements FileConstants {
|
||||
assert StringUtil.endsWithIgnoreCase(childPath.getFileName().toString(), FILE_EXT);
|
||||
resource = factory.createChildFileResource(childLocator, session, childPath);
|
||||
}
|
||||
result.add(resource);
|
||||
if (resource.exists()) {
|
||||
result.add(resource);
|
||||
}
|
||||
} catch (DecryptFailedException e) {
|
||||
LOG.warn("Decryption of resource failed: " + childPath);
|
||||
continue;
|
||||
@@ -238,6 +244,11 @@ class EncryptedDir extends AbstractEncryptedNode implements FileConstants {
|
||||
if (dirPath == null) {
|
||||
throw new DavException(DavServletResponse.SC_NOT_FOUND);
|
||||
}
|
||||
// https://tools.ietf.org/html/rfc4918#section-9.6
|
||||
// we must unlock anything we want to delete
|
||||
for (ActiveLock lock : member.getLocks()) {
|
||||
member.unlock(lock.getToken());
|
||||
}
|
||||
try {
|
||||
final String cleartextFilename = FilenameUtils.getName(member.getResourcePath());
|
||||
final String ciphertextFilename;
|
||||
@@ -250,7 +261,7 @@ class EncryptedDir extends AbstractEncryptedNode implements FileConstants {
|
||||
}
|
||||
final Path subDirPath = subDir.getDirectoryPath();
|
||||
if (subDirPath != null) {
|
||||
Files.deleteIfExists(subDirPath);
|
||||
Files.walkFileTree(subDirPath, new DeletingFileVisitor());
|
||||
}
|
||||
ciphertextFilename = filenameTranslator.getEncryptedDirFileName(cleartextFilename);
|
||||
} else {
|
||||
@@ -340,5 +351,33 @@ class EncryptedDir extends AbstractEncryptedNode implements FileConstants {
|
||||
public void spool(OutputContext outputContext) throws IOException {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package org.cryptomator.crypto.exceptions;
|
||||
|
||||
public class CounterOverflowException extends EncryptFailedException {
|
||||
private static final long serialVersionUID = 380066751064534731L;
|
||||
|
||||
public CounterOverflowException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -185,7 +185,8 @@ public class MainController extends AbstractFXMLViewController implements Initia
|
||||
* @param path non-null, writable, existing directory
|
||||
*/
|
||||
public void addVault(final Path path, boolean select) {
|
||||
if (path == null || !Files.isWritable(path)) {
|
||||
// TODO: Files.isWritable is broken on windows. Fix in Java 8u72, see https://bugs.openjdk.java.net/browse/JDK-8034057
|
||||
if (path == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user