diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/AbstractEncryptedNode.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/AbstractEncryptedNode.java index ead8ea246..c9f6158ed 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/AbstractEncryptedNode.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/AbstractEncryptedNode.java @@ -48,19 +48,19 @@ abstract class AbstractEncryptedNode implements DavResource { protected final DavSession session; protected final LockManager lockManager; protected final Cryptor cryptor; + protected final Path filePath; protected final DavPropertySet properties; - protected AbstractEncryptedNode(CryptoResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor) { + protected AbstractEncryptedNode(CryptoResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor, Path filePath) { this.factory = factory; this.locator = locator; this.session = session; this.lockManager = lockManager; this.cryptor = cryptor; + this.filePath = filePath; this.properties = new DavPropertySet(); } - protected abstract Path getPhysicalPath(); - @Override public String getComplianceClass() { return DAV_COMPLIANCE_CLASSES; @@ -73,7 +73,7 @@ abstract class AbstractEncryptedNode implements DavResource { @Override public boolean exists() { - return Files.exists(getPhysicalPath()); + return Files.exists(filePath); } @Override @@ -105,7 +105,7 @@ abstract class AbstractEncryptedNode implements DavResource { @Override public long getModificationTime() { try { - return Files.getLastModifiedTime(getPhysicalPath()).toMillis(); + return Files.getLastModifiedTime(filePath).toMillis(); } catch (IOException e) { return -1; } @@ -133,17 +133,16 @@ abstract class AbstractEncryptedNode implements DavResource { LOG.info("Set property {}", property.getName()); try { - final Path path = getPhysicalPath(); if (DavPropertyName.CREATIONDATE.equals(property.getName()) && property.getValue() instanceof String) { final String createDateStr = (String) property.getValue(); final FileTime createTime = FileTimeUtils.fromRfc1123String(createDateStr); - final BasicFileAttributeView attrView = Files.getFileAttributeView(path, BasicFileAttributeView.class, LinkOption.NOFOLLOW_LINKS); + final BasicFileAttributeView attrView = Files.getFileAttributeView(filePath, BasicFileAttributeView.class, LinkOption.NOFOLLOW_LINKS); attrView.setTimes(null, null, createTime); LOG.info("Updating Creation Date: {}", createTime.toString()); } else if (DavPropertyName.GETLASTMODIFIED.equals(property.getName()) && property.getValue() instanceof String) { final String lastModifiedTimeStr = (String) property.getValue(); final FileTime lastModifiedTime = FileTimeUtils.fromRfc1123String(lastModifiedTimeStr); - final BasicFileAttributeView attrView = Files.getFileAttributeView(path, BasicFileAttributeView.class, LinkOption.NOFOLLOW_LINKS); + final BasicFileAttributeView attrView = Files.getFileAttributeView(filePath, BasicFileAttributeView.class, LinkOption.NOFOLLOW_LINKS); attrView.setTimes(lastModifiedTime, null, null); LOG.info("Updating Last Modified Date: {}", lastModifiedTime.toString()); } 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 bcffb89d6..8c7614eec 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 @@ -5,6 +5,7 @@ import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.nio.charset.StandardCharsets; +import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; @@ -48,61 +49,46 @@ public class CryptoResourceFactory implements DavResourceFactory, FileNamingConv @Override public final DavResource createResource(DavResourceLocator locator, DavServletRequest request, DavServletResponse response) throws DavException { - if (DavMethods.METHOD_MKCOL.equals(request.getMethod())) { - final String parentResourcePath = FilenameUtils.getFullPathNoEndSeparator(locator.getResourcePath()); - final Path parentDirectoryPath = createEncryptedDirectoryPath(parentResourcePath); - return new EncryptedDirDuringCreation(this, locator, request.getDavSession(), lockManager, cryptor, filenameTranslator, parentDirectoryPath); - } - if (locator.isRootLocation()) { - final Path dirpath = createEncryptedDirectoryPath(""); - return createDirectory(locator, request.getDavSession(), dirpath); + return createRootDirectory(locator, request.getDavSession()); } - final Path filepath = getEncryptedFilePath(locator.getResourcePath()); + final Path filePath = getEncryptedFilePath(locator.getResourcePath()); final Path dirFilePath = getEncryptedDirectoryFilePath(locator.getResourcePath()); final String rangeHeader = request.getHeader(HttpHeader.RANGE.asString()); - if (Files.exists(dirFilePath)) { - final Path dirPath = createEncryptedDirectoryPath(locator.getResourcePath()); - return createDirectory(locator, request.getDavSession(), dirPath); - } else if (Files.exists(filepath) && DavMethods.METHOD_GET.equals(request.getMethod()) && rangeHeader != null) { + if (Files.exists(dirFilePath) || DavMethods.METHOD_MKCOL.equals(request.getMethod())) { + return createDirectory(locator, request.getDavSession(), dirFilePath); + } else if (Files.exists(filePath) && DavMethods.METHOD_GET.equals(request.getMethod()) && rangeHeader != null) { response.setStatus(HttpStatus.SC_PARTIAL_CONTENT); - return createFilePart(locator, request.getDavSession(), request, filepath); - } else if (Files.exists(filepath) || DavMethods.METHOD_PUT.equals(request.getMethod())) { - return createFile(locator, request.getDavSession(), filepath); + return createFilePart(locator, request.getDavSession(), request, filePath); + } else if (Files.exists(filePath) || DavMethods.METHOD_PUT.equals(request.getMethod())) { + return createFile(locator, request.getDavSession(), filePath); } else { - return createNonExisting(locator, request.getDavSession()); + // e.g. for MOVE operations: + return createNonExisting(locator, request.getDavSession(), filePath, dirFilePath); } } @Override public final DavResource createResource(DavResourceLocator locator, DavSession session) throws DavException { if (locator.isRootLocation()) { - final Path dirpath = createEncryptedDirectoryPath(""); - return createDirectory(locator, session, dirpath); + return createRootDirectory(locator, session); } - final Path filepath = getEncryptedFilePath(locator.getResourcePath()); + final Path filePath = getEncryptedFilePath(locator.getResourcePath()); final Path dirFilePath = getEncryptedDirectoryFilePath(locator.getResourcePath()); if (Files.exists(dirFilePath)) { - final Path dirPath = createEncryptedDirectoryPath(locator.getResourcePath()); - return createDirectory(locator, session, dirPath); - } else if (Files.exists(filepath)) { - return createFile(locator, session, filepath); + return createDirectory(locator, session, dirFilePath); + } else if (Files.exists(filePath)) { + return createFile(locator, session, filePath); } else { - return createNonExisting(locator, session); + // e.g. for MOVE operations: + return createNonExisting(locator, session, filePath, dirFilePath); } } DavResource createChildDirectoryResource(DavResourceLocator locator, DavSession session, Path existingDirectoryFile) throws DavException { - try { - final String directoryId = new String(readAllBytesAtomically(existingDirectoryFile), StandardCharsets.UTF_8); - final String directory = cryptor.encryptDirectoryPath(directoryId, FileSystems.getDefault().getSeparator()); - final Path dirpath = dataRoot.resolve(directory); - return createDirectory(locator, session, dirpath); - } catch (IOException e) { - throw new DavException(DavServletResponse.SC_INTERNAL_SERVER_ERROR, e); - } + return createDirectory(locator, session, existingDirectoryFile); } DavResource createChildFileResource(DavResourceLocator locator, DavSession session, Path existingFile) throws DavException { @@ -184,12 +170,28 @@ public class CryptoResourceFactory implements DavResourceFactory, FileNamingConv 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, filenameTranslator, dirPath); + private EncryptedDir createRootDirectory(DavResourceLocator locator, DavSession session) throws DavException { + final Path rootFile = dataRoot.resolve(ROOT_FILE); + final Path rootDir = filenameTranslator.getEncryptedDirectoryPath(""); + try { + // make sure, root dir always exists. + // create dir first (because it fails silently, if alreay existing) + Files.createDirectories(rootDir); + Files.createFile(rootFile); + } catch (FileAlreadyExistsException e) { + // no-op + } catch (IOException e) { + throw new DavException(DavServletResponse.SC_INTERNAL_SERVER_ERROR); + } + return createDirectory(locator, session, dataRoot.resolve(ROOT_FILE)); } - private NonExistingNode createNonExisting(DavResourceLocator locator, DavSession session) { - return new NonExistingNode(this, locator, session, lockManager, cryptor); + private EncryptedDir createDirectory(DavResourceLocator locator, DavSession session, Path filePath) { + return new EncryptedDir(this, locator, session, lockManager, cryptor, filenameTranslator, filePath); + } + + private NonExistingNode createNonExisting(DavResourceLocator locator, DavSession session, Path filePath, Path dirFilePath) { + return new NonExistingNode(this, locator, session, lockManager, cryptor, filePath, dirFilePath); } /* IO support */ 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 acf254bb4..30f337547 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 @@ -15,9 +15,11 @@ import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.nio.channels.SeekableByteChannel; import java.nio.charset.StandardCharsets; +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; @@ -27,6 +29,7 @@ import java.util.UUID; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.jackrabbit.webdav.DavException; import org.apache.jackrabbit.webdav.DavResource; import org.apache.jackrabbit.webdav.DavResourceIterator; @@ -53,21 +56,44 @@ import org.slf4j.LoggerFactory; class EncryptedDir extends AbstractEncryptedNode implements FileNamingConventions { private static final Logger LOG = LoggerFactory.getLogger(EncryptedDir.class); - private final Path directoryPath; private final FilenameTranslator filenameTranslator; + private String directoryId; + private Path directoryPath; - public EncryptedDir(CryptoResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor, FilenameTranslator filenameTranslator, 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; + public EncryptedDir(CryptoResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor, FilenameTranslator filenameTranslator, Path filePath) { + super(factory, locator, session, lockManager, cryptor, filePath); this.filenameTranslator = filenameTranslator; determineProperties(); } - @Override - protected Path getPhysicalPath() { + /** + * @return Path or null, if directory does not yet exist. + */ + protected synchronized String getDirectoryId() { + if (directoryId == null) { + try (final FileChannel c = FileChannel.open(filePath, 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); + directoryId = new String(buffer.array(), StandardCharsets.UTF_8); + } catch (FileNotFoundException e) { + directoryId = null; + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + return directoryId; + } + + /** + * @return Path or null, if directory does not yet exist. + */ + private synchronized Path getDirectoryPath() { + if (directoryPath == null) { + final String dirId = getDirectoryId(); + if (dirId != null) { + directoryPath = filenameTranslator.getEncryptedDirectoryPath(directoryId); + } + } return directoryPath; } @@ -76,16 +102,15 @@ class EncryptedDir extends AbstractEncryptedNode implements FileNamingConvention return true; } - @Override - public boolean exists() { - assert Files.isDirectory(directoryPath); - return true; - } - @Override public long getModificationTime() { try { - return Files.getLastModifiedTime(directoryPath).toMillis(); + final Path dirPath = getDirectoryPath(); + if (dirPath == null) { + return -1; + } else { + return Files.getLastModifiedTime(dirPath).toMillis(); + } } catch (IOException e) { return -1; } @@ -108,13 +133,15 @@ class EncryptedDir extends AbstractEncryptedNode implements FileNamingConvention } } - @Deprecated private void addMemberDir(DavResourceLocator childLocator, InputContext inputContext) throws DavException { - LOG.warn("Invokation of addMemberDir(DavResourceLocator childLocator, InputContext inputContext)"); + final Path dirPath = getDirectoryPath(); + if (dirPath == null) { + throw new DavException(DavServletResponse.SC_NOT_FOUND); + } try { final String cleartextDirName = FilenameUtils.getName(childLocator.getResourcePath()); final String ciphertextDirName = filenameTranslator.getEncryptedDirName(cleartextDirName); - final Path dirFilePath = directoryPath.resolve(ciphertextDirName); + final Path dirFilePath = dirPath.resolve(ciphertextDirName); final String directoryId; if (Files.exists(dirFilePath)) { try (final FileChannel c = FileChannel.open(dirFilePath, StandardOpenOption.READ, StandardOpenOption.DSYNC); final FileLock lock = c.lock(0L, Long.MAX_VALUE, true)) { @@ -138,10 +165,14 @@ class EncryptedDir extends AbstractEncryptedNode implements FileNamingConvention } private void addMemberFile(DavResourceLocator childLocator, InputContext inputContext) throws DavException { + final Path dirPath = getDirectoryPath(); + if (dirPath == null) { + throw new DavException(DavServletResponse.SC_NOT_FOUND); + } try { final String cleartextFilename = FilenameUtils.getName(childLocator.getResourcePath()); final String ciphertextFilename = filenameTranslator.getEncryptedFilename(cleartextFilename); - final Path filePath = directoryPath.resolve(ciphertextFilename); + final Path filePath = dirPath.resolve(ciphertextFilename); try (final SeekableByteChannel channel = Files.newByteChannel(filePath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { cryptor.encryptFile(inputContext.getInputStream(), channel); } catch (SecurityException e) { @@ -164,7 +195,11 @@ class EncryptedDir extends AbstractEncryptedNode implements FileNamingConvention @Override public DavResourceIterator getMembers() { try { - final DirectoryStream directoryStream = Files.newDirectoryStream(directoryPath, DIRECTORY_CONTENT_FILTER); + final Path dirPath = getDirectoryPath(); + if (dirPath == null) { + throw new DavException(DavServletResponse.SC_NOT_FOUND); + } + final DirectoryStream directoryStream = Files.newDirectoryStream(dirPath, DIRECTORY_CONTENT_FILTER); final List result = new ArrayList<>(); for (final Path childPath : directoryStream) { @@ -205,6 +240,10 @@ class EncryptedDir extends AbstractEncryptedNode implements FileNamingConvention } private void removeMember(AbstractEncryptedNode member) throws DavException { + final Path dirPath = getDirectoryPath(); + if (dirPath == null) { + throw new DavException(DavServletResponse.SC_NOT_FOUND); + } try { final String cleartextFilename = FilenameUtils.getName(member.getResourcePath()); final String ciphertextFilename; @@ -215,12 +254,15 @@ class EncryptedDir extends AbstractEncryptedNode implements FileNamingConvention DavResource m = iterator.next(); member.removeMember(m); } - Files.deleteIfExists(subDir.directoryPath); + final Path subDirPath = subDir.getDirectoryPath(); + if (subDirPath != null) { + Files.deleteIfExists(subDirPath); + } ciphertextFilename = filenameTranslator.getEncryptedDirName(cleartextFilename); } else { ciphertextFilename = filenameTranslator.getEncryptedFilename(cleartextFilename); } - final Path memberPath = directoryPath.resolve(ciphertextFilename); + final Path memberPath = dirPath.resolve(ciphertextFilename); Files.deleteIfExists(memberPath); } catch (FileNotFoundException e) { // no-op @@ -231,50 +273,81 @@ class EncryptedDir extends AbstractEncryptedNode implements FileNamingConvention @Override public void move(AbstractEncryptedNode dest) throws DavException, IOException { - 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); - // } + // when moving a directory we only need to move the file (actual dir is ID-dependent and won't change) + final Path srcPath = filePath; + final Path dstPath; + if (dest instanceof NonExistingNode) { + dstPath = ((NonExistingNode) dest).getDirFilePath(); + } else { + dstPath = dest.filePath; + } + + // move: + Files.createDirectories(dstPath.getParent()); + try { + Files.move(srcPath, dstPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); + } catch (AtomicMoveNotSupportedException e) { + Files.move(srcPath, dstPath, StandardCopyOption.REPLACE_EXISTING); + } } @Override public void copy(AbstractEncryptedNode dest, boolean shallow) throws DavException, IOException { - 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); - // } + final Path dstDirFilePath; + if (dest instanceof NonExistingNode) { + dstDirFilePath = ((NonExistingNode) dest).getDirFilePath(); + } else { + dstDirFilePath = dest.filePath; + } + + // copy dirFile: + final String srcDirId = getDirectoryId(); + if (srcDirId == null) { + throw new DavException(DavServletResponse.SC_NOT_FOUND); + } + final String dstDirId = UUID.randomUUID().toString(); + try (final FileChannel c = FileChannel.open(dstDirFilePath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.DSYNC); final FileLock lock = c.lock()) { + c.write(ByteBuffer.wrap(dstDirId.getBytes(StandardCharsets.UTF_8))); + } + + // copy actual dir: + if (!shallow) { + copyDirectoryContents(srcDirId, dstDirId); + } else { + final Path dstDirPath = filenameTranslator.getEncryptedDirectoryPath(dstDirId); + Files.createDirectories(dstDirPath); + } + } + + private void copyDirectoryContents(String srcDirId, String dstDirId) throws IOException { + final Path srcDirPath = filenameTranslator.getEncryptedDirectoryPath(srcDirId); + final Path dstDirPath = filenameTranslator.getEncryptedDirectoryPath(dstDirId); + Files.createDirectories(dstDirPath); + final DirectoryStream directoryStream = Files.newDirectoryStream(srcDirPath, DIRECTORY_CONTENT_FILTER); + for (final Path srcChildPath : directoryStream) { + final String childName = srcChildPath.getFileName().toString(); + final Path dstChildPath = dstDirPath.resolve(childName); + if (StringUtils.endsWithIgnoreCase(childName, FILE_EXT)) { + try { + Files.copy(srcChildPath, dstChildPath, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); + } catch (AtomicMoveNotSupportedException e) { + Files.copy(srcChildPath, dstChildPath, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING); + } + } else if (StringUtils.endsWithIgnoreCase(childName, DIR_EXT)) { + final String srcSubdirId; + try (final FileChannel c = FileChannel.open(srcChildPath, 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); + srcSubdirId = new String(buffer.array(), StandardCharsets.UTF_8); + } + final String dstSubdirId = UUID.randomUUID().toString(); + try (final FileChannel c = FileChannel.open(dstChildPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.DSYNC); + final FileLock lock = c.lock()) { + c.write(ByteBuffer.wrap(dstSubdirId.getBytes(StandardCharsets.UTF_8))); + } + copyDirectoryContents(srcSubdirId, dstSubdirId); + } + } } @Override @@ -287,11 +360,13 @@ class EncryptedDir extends AbstractEncryptedNode implements FileNamingConvention properties.add(new ResourceType(ResourceType.COLLECTION)); properties.add(new DefaultDavProperty(DavPropertyName.ISCOLLECTION, 1)); try { - final BasicFileAttributes attrs = Files.readAttributes(directoryPath, BasicFileAttributes.class); - properties.add(new DefaultDavProperty(DavPropertyName.CREATIONDATE, FileTimeUtils.toRfc1123String(attrs.creationTime()))); - properties.add(new DefaultDavProperty(DavPropertyName.GETLASTMODIFIED, FileTimeUtils.toRfc1123String(attrs.lastModifiedTime()))); + if (Files.exists(filePath)) { + final BasicFileAttributes attrs = Files.readAttributes(filePath, BasicFileAttributes.class); + properties.add(new DefaultDavProperty(DavPropertyName.CREATIONDATE, FileTimeUtils.toRfc1123String(attrs.creationTime()))); + properties.add(new DefaultDavProperty(DavPropertyName.GETLASTMODIFIED, FileTimeUtils.toRfc1123String(attrs.lastModifiedTime()))); + } } catch (IOException e) { - LOG.error("Error determining metadata " + directoryPath.toString(), e); + LOG.error("Error determining metadata " + filePath, e); // don't add any further properties } } diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedDirDuringCreation.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedDirDuringCreation.java deleted file mode 100644 index cce4bab0b..000000000 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedDirDuringCreation.java +++ /dev/null @@ -1,114 +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.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.FileAlreadyExistsException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.time.Instant; -import java.util.UUID; - -import org.apache.commons.io.FilenameUtils; -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.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.LockManager; -import org.cryptomator.crypto.Cryptor; - -class EncryptedDirDuringCreation extends AbstractEncryptedNode { - - private final Path parentDir; - private final FilenameTranslator filenameTranslator; - - public EncryptedDirDuringCreation(CryptoResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor, FilenameTranslator filenameTranslator, Path parentDir) { - super(factory, locator, session, lockManager, cryptor); - this.parentDir = parentDir; - this.filenameTranslator = filenameTranslator; - } - - public void doCreate() throws DavException { - try { - final String cleartextDirName = FilenameUtils.getName(locator.getResourcePath()); - final String ciphertextDirName = filenameTranslator.getEncryptedDirName(cleartextDirName); - final Path dirFilePath = parentDir.resolve(ciphertextDirName); - final String directoryId = UUID.randomUUID().toString(); - try (final FileChannel c = FileChannel.open(dirFilePath, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW, StandardOpenOption.DSYNC); final FileLock lock = c.lock()) { - c.write(ByteBuffer.wrap(directoryId.getBytes(StandardCharsets.UTF_8))); - } catch (FileAlreadyExistsException e) { - throw new DavException(DavServletResponse.SC_METHOD_NOT_ALLOWED); - } - final Path directoryPath = filenameTranslator.getEncryptedDirectoryPath(directoryId); - Files.createDirectories(directoryPath); - } catch (IOException e) { - throw new DavException(DavServletResponse.SC_INTERNAL_SERVER_ERROR); - } - } - - @Override - protected Path getPhysicalPath() { - throw new UnsupportedOperationException("Resource doesn't exist."); - } - - @Override - public boolean exists() { - return false; - } - - @Override - public boolean isCollection() { - return true; - } - - @Override - public long getModificationTime() { - return Instant.now().toEpochMilli(); - } - - @Override - public void spool(OutputContext outputContext) throws IOException { - throw new UnsupportedOperationException("Resource doesn't exist."); - } - - @Override - public void addMember(DavResource resource, InputContext inputContext) throws DavException { - throw new UnsupportedOperationException("Resource doesn't exist."); - } - - @Override - public DavResourceIterator getMembers() { - throw new UnsupportedOperationException("Resource doesn't exist."); - } - - @Override - public void removeMember(DavResource member) throws DavException { - throw new UnsupportedOperationException("Resource doesn't exist."); - } - - @Override - public void move(AbstractEncryptedNode destination) throws DavException { - throw new UnsupportedOperationException("Resource doesn't exist."); - } - - @Override - public void copy(AbstractEncryptedNode destination, boolean shallow) throws DavException { - throw new UnsupportedOperationException("Resource doesn't exist."); - } - -} diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFile.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFile.java index 968478030..5f20bc661 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFile.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFile.java @@ -11,8 +11,10 @@ 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; @@ -40,23 +42,16 @@ 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, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor, CryptoWarningHandler cryptoWarningHandler, Path filePath) { - super(factory, locator, session, lockManager, cryptor); + super(factory, locator, session, lockManager, cryptor, filePath); if (filePath == null) { throw new IllegalArgumentException("filePath must not be null"); } this.cryptoWarningHandler = cryptoWarningHandler; - this.filePath = filePath; this.determineProperties(); } - @Override - protected Path getPhysicalPath() { - return filePath; - } - @Override public boolean isCollection() { return false; @@ -128,40 +123,36 @@ class EncryptedFile extends AbstractEncryptedNode { @Override public void move(AbstractEncryptedNode dest) throws DavException, IOException { - 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); - // } + final Path srcPath = filePath; + final Path dstPath; + if (dest instanceof NonExistingNode) { + dstPath = ((NonExistingNode) dest).getFilePath(); + } else { + dstPath = dest.filePath; + } + + try { + Files.move(srcPath, dstPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); + } catch (AtomicMoveNotSupportedException e) { + Files.move(srcPath, dstPath, StandardCopyOption.REPLACE_EXISTING); + } } @Override public void copy(AbstractEncryptedNode dest, boolean shallow) throws DavException, IOException { - 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); - // } + final Path srcPath = filePath; + final Path dstPath; + if (dest instanceof NonExistingNode) { + dstPath = ((NonExistingNode) dest).getFilePath(); + } else { + dstPath = dest.filePath; + } + + try { + Files.copy(srcPath, dstPath, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); + } catch (AtomicMoveNotSupportedException e) { + Files.copy(srcPath, dstPath, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING); + } } } diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FileNamingConventions.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FileNamingConventions.java index 7963a84b9..49a9493a4 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FileNamingConventions.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FileNamingConventions.java @@ -21,9 +21,14 @@ interface FileNamingConventions { /** * Maximum path length on some file systems or cloud storage providers is restricted.
* Parent folder path uses up to 58 chars (sha256 -> 32 bytes base32 encoded to 56 bytes + two slashes). That in mind we don't want the total path to be longer than 255 chars.
- * 128 chars would be enought for up to 80 plaintext chars. Also we need up to 8 chars for our file extension. So lets use {@value #ENCRYPTED_FILENAME_LENGTH_LIMIT}. + * 128 chars would be enought for up to 80 plaintext chars. Also we need up to 9 chars for our file extension. So lets use {@value #ENCRYPTED_FILENAME_LENGTH_LIMIT}. */ - int ENCRYPTED_FILENAME_LENGTH_LIMIT = 136; + int ENCRYPTED_FILENAME_LENGTH_LIMIT = 137; + + /** + * Dummy file, on which file attributes can be stored for the root directory. + */ + String ROOT_FILE = "root"; /** * For encrypted directory names <= {@value #ENCRYPTED_FILENAME_LENGTH_LIMIT} chars. diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/NonExistingNode.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/NonExistingNode.java index 27cd3adbc..8d194a1cb 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/NonExistingNode.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/NonExistingNode.java @@ -19,17 +19,18 @@ 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.LockManager; +import org.apache.jackrabbit.webdav.property.DavProperty; import org.cryptomator.crypto.Cryptor; class NonExistingNode extends AbstractEncryptedNode { - public NonExistingNode(CryptoResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor) { - super(factory, locator, session, lockManager, cryptor); - } + private final Path filePath; + private final Path dirFilePath; - @Override - protected Path getPhysicalPath() { - throw new UnsupportedOperationException("Resource doesn't exist."); + public NonExistingNode(CryptoResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor, Path filePath, Path dirFilePath) { + super(factory, locator, session, lockManager, cryptor, null); + this.filePath = filePath; + this.dirFilePath = dirFilePath; } @Override @@ -77,4 +78,17 @@ class NonExistingNode extends AbstractEncryptedNode { throw new UnsupportedOperationException("Resource doesn't exist."); } + @Override + public void setProperty(DavProperty property) throws DavException { + throw new UnsupportedOperationException("Resource doesn't exist."); + } + + public Path getFilePath() { + return filePath; + } + + public Path getDirFilePath() { + return dirFilePath; + } + } diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java index cef5f2a4d..ee9893e86 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java @@ -8,7 +8,6 @@ ******************************************************************************/ package org.cryptomator.webdav.jackrabbit; -import java.io.IOException; import java.util.Collection; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -17,14 +16,11 @@ import java.util.concurrent.TimeUnit; import javax.servlet.ServletConfig; import javax.servlet.ServletException; -import org.apache.jackrabbit.webdav.DavException; import org.apache.jackrabbit.webdav.DavLocatorFactory; import org.apache.jackrabbit.webdav.DavResource; import org.apache.jackrabbit.webdav.DavResourceFactory; -import org.apache.jackrabbit.webdav.DavServletResponse; import org.apache.jackrabbit.webdav.DavSessionProvider; import org.apache.jackrabbit.webdav.WebdavRequest; -import org.apache.jackrabbit.webdav.WebdavResponse; import org.apache.jackrabbit.webdav.server.AbstractWebdavServlet; import org.cryptomator.crypto.Cryptor; @@ -71,16 +67,16 @@ public class WebDavServlet extends AbstractWebdavServlet { } } - @Override - protected void doMkCol(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { - if (resource instanceof EncryptedDirDuringCreation) { - EncryptedDirDuringCreation dir = (EncryptedDirDuringCreation) resource; - dir.doCreate(); - response.setStatus(DavServletResponse.SC_CREATED); - } else { - - } - } + // @Override + // protected void doMkCol(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { + // if (resource instanceof EncryptedDirDuringCreation) { + // EncryptedDirDuringCreation dir = (EncryptedDirDuringCreation) resource; + // dir.doCreate(); + // response.setStatus(DavServletResponse.SC_CREATED); + // } else { + // + // } + // } @Override protected boolean isPreconditionValid(WebdavRequest request, DavResource resource) {