mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-17 02:01:27 +00:00
yet another refactoring session (functionality restored now)
This commit is contained in:
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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 <code>null</code>, 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 <code>null</code>, 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<Path> directoryStream = Files.newDirectoryStream(directoryPath, DIRECTORY_CONTENT_FILTER);
|
||||
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 List<DavResource> 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<Path> 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<Integer>(DavPropertyName.ISCOLLECTION, 1));
|
||||
try {
|
||||
final BasicFileAttributes attrs = Files.readAttributes(directoryPath, BasicFileAttributes.class);
|
||||
properties.add(new DefaultDavProperty<String>(DavPropertyName.CREATIONDATE, FileTimeUtils.toRfc1123String(attrs.creationTime())));
|
||||
properties.add(new DefaultDavProperty<String>(DavPropertyName.GETLASTMODIFIED, FileTimeUtils.toRfc1123String(attrs.lastModifiedTime())));
|
||||
if (Files.exists(filePath)) {
|
||||
final BasicFileAttributes attrs = Files.readAttributes(filePath, BasicFileAttributes.class);
|
||||
properties.add(new DefaultDavProperty<String>(DavPropertyName.CREATIONDATE, FileTimeUtils.toRfc1123String(attrs.creationTime())));
|
||||
properties.add(new DefaultDavProperty<String>(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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,9 +21,14 @@ interface FileNamingConventions {
|
||||
/**
|
||||
* Maximum path length on some file systems or cloud storage providers is restricted.<br/>
|
||||
* 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.<br/>
|
||||
* 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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user