several refactorings, especially concerning LOCK operations on windows

This commit is contained in:
Sebastian Stenzel
2015-10-28 22:44:59 +01:00
parent b6a5db5797
commit 1a81b3a781
6 changed files with 89 additions and 49 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}