diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoLocator.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoLocator.java index 12cf9a761..3f998870a 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoLocator.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoLocator.java @@ -1,10 +1,21 @@ 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.Arrays; +import java.util.List; +import java.util.UUID; 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.DavResourceLocator; @@ -69,7 +80,7 @@ class CryptoLocator implements DavResourceLocator { @Override public String getRepositoryPath() { if (isRootLocation()) { - return getDirectoryPath(); + return getEncryptedRootDirectoryPath(); } try { final String plaintextPath = getResourcePath(); @@ -88,18 +99,61 @@ class CryptoLocator implements DavResourceLocator { * 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). + * @throws IOException */ - public String getDirectoryPath() { - final String ciphertextPath = cryptor.encryptDirectoryPath(getResourcePath(), FileSystems.getDefault().getSeparator()); - return rootPath.resolve(ciphertextPath).toString(); + public String getDirectoryPath(boolean create) throws IOException { + if (isRootLocation()) { + return getEncryptedRootDirectoryPath(); + } else { + final List cleartextPathComponents = Arrays.asList(StringUtils.split(getResourcePath(), "/")); + return getEncryptedDirectoryPath(rootPath, cleartextPathComponents, false).toString(); + } + } + + private Path getEncryptedDirectoryPath(Path encryptedParentDirectoryPath, List cleartextSubPathComponents, boolean create) throws IOException { + if (cleartextSubPathComponents.size() == 0) { + return encryptedParentDirectoryPath; + } else { + final String nextPathComponent = cleartextSubPathComponents.get(0); + final List remainingSubPathComponents = cleartextSubPathComponents.subList(1, cleartextSubPathComponents.size()); + final String fullEncryptedSubdirectoryPath = getEncryptedDirectoryPath(encryptedParentDirectoryPath, nextPathComponent, create); + return getEncryptedDirectoryPath(rootPath.resolve(fullEncryptedSubdirectoryPath), remainingSubPathComponents, create); + } + } + + private String getEncryptedDirectoryPath(Path encryptedParentDirectoryPath, String cleartextDirectoryName, boolean create) throws IOException { + final String encryptedDirectoryName = this.cryptor.encryptFilename(cleartextDirectoryName, this.factory); + // TODO file extensions... + final Path directoryFile = encryptedParentDirectoryPath.resolve(encryptedDirectoryName + ".dir"); + if (Files.exists(directoryFile)) { + try (final FileChannel c = FileChannel.open(directoryFile, 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); + final String directoryUuid = buffer.asCharBuffer().toString(); + return this.cryptor.encryptDirectoryPath(directoryUuid, FileSystems.getDefault().getSeparator()); + } + } else if (create) { + try (final FileChannel c = FileChannel.open(directoryFile, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.DSYNC); final FileLock lock = c.lock()) { + final String directoryUuid = UUID.randomUUID().toString(); + final ByteBuffer buf = ByteBuffer.wrap(directoryUuid.getBytes(StandardCharsets.UTF_8)); + c.write(buf); + return this.cryptor.encryptDirectoryPath(directoryUuid, FileSystems.getDefault().getSeparator()); + } + } else { + throw new FileNotFoundException(directoryFile.toString()); + } + } + + private String getEncryptedRootDirectoryPath() { + return this.cryptor.encryptDirectoryPath("", FileSystems.getDefault().getSeparator()); } public Path getEncryptedFilePath() { return FileSystems.getDefault().getPath(getRepositoryPath()); } - public Path getEncryptedDirectoryPath() { - return FileSystems.getDefault().getPath(getDirectoryPath()); + public Path getEncryptedDirectoryPath(boolean create) throws IOException { + return FileSystems.getDefault().getPath(getDirectoryPath(create)); } /* other stuff */ 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 6a965f5e4..990c9029d 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 @@ -1,5 +1,7 @@ package org.cryptomator.webdav.jackrabbit; +import java.io.FileNotFoundException; +import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; @@ -17,6 +19,7 @@ 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.cryptomator.webdav.exceptions.IORuntimeException; import org.eclipse.jetty.http.HttpHeader; public class CryptoResourceFactory implements DavResourceFactory { @@ -52,10 +55,17 @@ public class CryptoResourceFactory implements DavResourceFactory { 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()); + 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 String rangeHeader = request.getHeader(HttpHeader.RANGE.asString()); - if (Files.isDirectory(dirpath) || DavMethods.METHOD_MKCOL.equals(request.getMethod())) { + if (Files.isDirectory(dirpath)) { return createDirectory(locator, request.getDavSession()); } else if (Files.isRegularFile(filepath) && DavMethods.METHOD_GET.equals(request.getMethod()) && rangeHeader != null) { response.setStatus(HttpStatus.SC_PARTIAL_CONTENT); @@ -69,7 +79,14 @@ public class CryptoResourceFactory implements DavResourceFactory { 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()); + Path dirpath = null; + try { + dirpath = FileSystems.getDefault().getPath(locator.getDirectoryPath(false)); + } catch (FileNotFoundException e) { + // no-op + } catch (IOException e) { + throw new IORuntimeException(e); + } if (Files.isDirectory(dirpath)) { return createDirectory(locator, session); 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 aec5a60e8..7fbeb6202 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 @@ -8,6 +8,7 @@ ******************************************************************************/ package org.cryptomator.webdav.jackrabbit; +import java.io.FileNotFoundException; import java.io.IOException; import java.nio.channels.SeekableByteChannel; import java.nio.file.AtomicMoveNotSupportedException; @@ -53,7 +54,11 @@ class EncryptedDir extends AbstractEncryptedNode { @Override protected Path getPhysicalPath() { - return locator.getEncryptedDirectoryPath(); + try { + return locator.getEncryptedDirectoryPath(false); + } catch (IOException e) { + throw new IORuntimeException(e); + } } @Override @@ -63,13 +68,17 @@ class EncryptedDir extends AbstractEncryptedNode { @Override public boolean exists() { - return Files.isDirectory(locator.getEncryptedDirectoryPath()); + try { + return Files.isDirectory(locator.getEncryptedDirectoryPath(false)); + } catch (IOException e) { + return false; + } } @Override public long getModificationTime() { try { - return Files.getLastModifiedTime(locator.getEncryptedDirectoryPath()).toMillis(); + return Files.getLastModifiedTime(locator.getEncryptedDirectoryPath(false)).toMillis(); } catch (IOException e) { return -1; } @@ -94,8 +103,7 @@ class EncryptedDir extends AbstractEncryptedNode { private void addMemberDir(CryptoLocator childLocator, InputContext inputContext) throws DavException { try { - Files.createDirectories(childLocator.getEncryptedFilePath()); - Files.createDirectories(childLocator.getEncryptedDirectoryPath()); + Files.createDirectories(childLocator.getEncryptedDirectoryPath(true)); } catch (SecurityException e) { throw new DavException(DavServletResponse.SC_FORBIDDEN, e); } catch (IOException e) { @@ -126,7 +134,7 @@ class EncryptedDir extends AbstractEncryptedNode { @Override public DavResourceIterator getMembers() { try { - final DirectoryStream directoryStream = Files.newDirectoryStream(locator.getEncryptedDirectoryPath(), cryptor.getPayloadFilesFilter()); + final DirectoryStream directoryStream = Files.newDirectoryStream(locator.getEncryptedDirectoryPath(false), cryptor.getPayloadFilesFilter()); final List result = new ArrayList<>(); for (final Path childPath : directoryStream) { @@ -162,11 +170,13 @@ class EncryptedDir extends AbstractEncryptedNode { private void removeMember(AbstractEncryptedNode member) { try { + Files.deleteIfExists(member.getLocator().getEncryptedFilePath()); if (member.isCollection()) { member.getMembers().forEachRemaining(m -> securelyRemoveMemberOfCollection(member, m)); - Files.deleteIfExists(member.getLocator().getEncryptedDirectoryPath()); + Files.deleteIfExists(member.getLocator().getEncryptedDirectoryPath(false)); } - Files.deleteIfExists(member.getLocator().getEncryptedFilePath()); + } catch (FileNotFoundException e) { + // no-op } catch (IOException e) { throw new IORuntimeException(e); } @@ -182,8 +192,8 @@ class EncryptedDir extends AbstractEncryptedNode { @Override public void move(AbstractEncryptedNode dest) throws DavException, IOException { - final Path srcDir = this.locator.getEncryptedDirectoryPath(); - final Path dstDir = dest.locator.getEncryptedDirectoryPath(); + 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(); @@ -205,8 +215,8 @@ class EncryptedDir extends AbstractEncryptedNode { @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 srcDir = this.locator.getEncryptedDirectoryPath(false); + final Path dstDir = dest.locator.getEncryptedDirectoryPath(true); final Path srcFile = this.locator.getEncryptedFilePath(); final Path dstFile = dest.locator.getEncryptedFilePath(); @@ -233,7 +243,12 @@ class EncryptedDir extends AbstractEncryptedNode { @Override protected void determineProperties() { - final Path path = locator.getEncryptedDirectoryPath(); + Path path; + try { + path = locator.getEncryptedDirectoryPath(false); + } catch (IOException e) { + throw new IORuntimeException(e); + } properties.add(new ResourceType(ResourceType.COLLECTION)); properties.add(new DefaultDavProperty(DavPropertyName.ISCOLLECTION, 1)); if (Files.exists(path)) { diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/ResourcePathUtils.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/ResourcePathUtils.java deleted file mode 100644 index 6f77bff1f..000000000 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/ResourcePathUtils.java +++ /dev/null @@ -1,28 +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.FileSystems; -import java.nio.file.Path; - -final class ResourcePathUtils { - - private ResourcePathUtils() { - throw new IllegalStateException("not instantiable"); - } - - public static Path getPhysicalFilePath(CryptoLocator locator) { - return FileSystems.getDefault().getPath(locator.getRepositoryPath()); - } - - public static Path getPhysicalDirectoryPath(CryptoLocator locator) { - return FileSystems.getDefault().getPath(locator.getDirectoryPath()); - } - -}