diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CleartextLocatorFactory.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CleartextLocatorFactory.java index 560f0a5bc..d4c696109 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CleartextLocatorFactory.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CleartextLocatorFactory.java @@ -5,7 +5,6 @@ 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 { diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoResourceFactory.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoResourceFactory.java index 55ee00f79..6b3cce690 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoResourceFactory.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoResourceFactory.java @@ -22,7 +22,6 @@ 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.eclipse.jetty.http.HttpHeader; diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedDir.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedDir.java index dbe293b56..983365705 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedDir.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedDir.java @@ -16,7 +16,6 @@ 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; @@ -26,9 +25,10 @@ import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileTime; import java.time.Instant; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; +import java.util.Queue; import java.util.UUID; +import java.util.concurrent.LinkedTransferQueue; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; @@ -169,7 +169,7 @@ class EncryptedDir extends AbstractEncryptedNode implements FileConstants { final Path tmpFilePath = Files.createTempFile(dirPath, null, null); // encrypt to tmp file: try (final FileChannel c = FileChannel.open(tmpFilePath, StandardOpenOption.WRITE, StandardOpenOption.DSYNC)) { - long asd = cryptor.encryptFile(inputContext.getInputStream(), c); + cryptor.encryptFile(inputContext.getInputStream(), c); } catch (SecurityException e) { throw new DavException(DavServletResponse.SC_FORBIDDEN, e); } catch (EncryptFailedException e) { @@ -245,30 +245,21 @@ class EncryptedDir extends AbstractEncryptedNode implements FileConstants { throw new DavException(DavServletResponse.SC_NOT_FOUND); } // https://tools.ietf.org/html/rfc4918#section-9.6 - // we must unlock anything we want to delete + // we must unlock anything we want to delete: for (ActiveLock lock : member.getLocks()) { member.unlock(lock.getToken()); } + // now we can delete the file or directory: try { final String cleartextFilename = FilenameUtils.getName(member.getResourcePath()); - final String ciphertextFilename; if (member instanceof EncryptedDir) { final EncryptedDir subDir = (EncryptedDir) member; - // remove sub-members recursively before deleting own directory - for (Iterator iterator = member.getMembers(); iterator.hasNext();) { - DavResource m = iterator.next(); - member.removeMember(m); - } - final Path subDirPath = subDir.getDirectoryPath(); - if (subDirPath != null) { - Files.walkFileTree(subDirPath, new DeletingFileVisitor()); - } - ciphertextFilename = filenameTranslator.getEncryptedDirFileName(cleartextFilename); + deleteSubDirectory(subDir); } else { - ciphertextFilename = filenameTranslator.getEncryptedFilename(cleartextFilename); + final String ciphertextFilename = filenameTranslator.getEncryptedFilename(cleartextFilename); + final Path memberPath = dirPath.resolve(ciphertextFilename); + Files.deleteIfExists(memberPath); } - final Path memberPath = dirPath.resolve(ciphertextFilename); - Files.deleteIfExists(memberPath); } catch (FileNotFoundException e) { // no-op } catch (IOException e) { @@ -353,21 +344,65 @@ class EncryptedDir extends AbstractEncryptedNode implements FileConstants { } /** - * Deletes all files and folders, it visits. + * Deletes a given directory recursively by resolving subdirectories using their directory files. */ - private static class DeletingFileVisitor extends SimpleFileVisitor { + private void deleteSubDirectory(final EncryptedDir subDir) throws IOException { + final Path subDirPath = subDir.getDirectoryPath(); + filenameTranslator.uncacheDirectoryId(subDir.filePath); + Files.delete(subDir.filePath); + final LinkedTransferQueue queue = new LinkedTransferQueue<>(); + queue.put(subDirPath); + Path dir; + while ((dir = queue.poll()) != null) { + if (Files.exists(dir)) { + Files.walkFileTree(dir, new RecursiveDirectoryDeletingVisitor(queue)); + } + } + } + + /** + * Deletes all files it visits and enqueues subdirectories into a given {@link Queue} for deletion, too. + * + * If its parent directory is empty after deleting, it will get deleted, too. + */ + private class RecursiveDirectoryDeletingVisitor extends SimpleFileVisitor { + + private final Queue directories; + + private RecursiveDirectoryDeletingVisitor(Queue directories) { + this.directories = directories; + } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) throws IOException { - if (attributes.isRegularFile()) { - Files.delete(file); + if (file.toString().endsWith(DIR_EXT)) { + final String directoryId = filenameTranslator.getDirectoryId(file, false); + final Path directoryPath = filenameTranslator.getEncryptedDirectoryPath(directoryId); + directories.add(directoryPath); + filenameTranslator.uncacheDirectoryId(file); } + Files.delete(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + // first check, if we're the only remaining child: + boolean hasSiblings = false; + try (final DirectoryStream siblings = Files.newDirectoryStream(dir.getParent())) { + for (Path sibling : siblings) { + if (!dir.getFileName().equals(sibling.getFileName())) { + hasSiblings = true; + break; + } + } + } + // delete our current directory: Files.delete(dir); + // if we have siblings, we still need our parent. Otherwise delete it, too: + if (!hasSiblings) { + Files.delete(dir.getParent()); + } return FileVisitResult.CONTINUE; } diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FilenameTranslator.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FilenameTranslator.java index b05fbf598..95f46cf8d 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FilenameTranslator.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FilenameTranslator.java @@ -68,6 +68,14 @@ class FilenameTranslator implements FileConstants { } } } + + /** + * to be called when a directory gets deleted, so the corresponding directory id is not longer cached. + */ + public void uncacheDirectoryId(Path directoryFile) throws IOException { + final Pair key = ImmutablePair.of(directoryFile, Files.getLastModifiedTime(directoryFile)); + directoryIdCache.remove(key); + } public Path getEncryptedDirectoryPath(String directoryId) { final String encrypted = cryptor.encryptDirectoryPath(directoryId, FileSystems.getDefault().getSeparator());