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 c9f6158ed..a147d9fd1 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 @@ -13,6 +13,7 @@ import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributeView; +import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileTime; import java.util.List; @@ -32,6 +33,7 @@ import org.apache.jackrabbit.webdav.property.DavProperty; import org.apache.jackrabbit.webdav.property.DavPropertyName; import org.apache.jackrabbit.webdav.property.DavPropertyNameSet; import org.apache.jackrabbit.webdav.property.DavPropertySet; +import org.apache.jackrabbit.webdav.property.DefaultDavProperty; import org.apache.jackrabbit.webdav.property.PropEntry; import org.cryptomator.crypto.Cryptor; import org.cryptomator.webdav.exceptions.IORuntimeException; @@ -59,6 +61,15 @@ abstract class AbstractEncryptedNode implements DavResource { this.cryptor = cryptor; this.filePath = filePath; this.properties = new DavPropertySet(); + if (filePath != null && Files.exists(filePath)) { + try { + 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 " + filePath.toString(), e); + } + } } @Override @@ -132,22 +143,24 @@ abstract class AbstractEncryptedNode implements DavResource { LOG.info("Set property {}", property.getName()); - try { - 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(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(filePath, BasicFileAttributeView.class, LinkOption.NOFOLLOW_LINKS); - attrView.setTimes(lastModifiedTime, null, null); - LOG.info("Updating Last Modified Date: {}", lastModifiedTime.toString()); + if (Files.exists(filePath)) { + try { + 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(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(filePath, BasicFileAttributeView.class, LinkOption.NOFOLLOW_LINKS); + attrView.setTimes(lastModifiedTime, null, null); + LOG.info("Updating Last Modified Date: {}", lastModifiedTime.toString()); + } + } catch (IOException e) { + throw new DavException(DavServletResponse.SC_INTERNAL_SERVER_ERROR); } - } catch (IOException e) { - throw new DavException(DavServletResponse.SC_INTERNAL_SERVER_ERROR); } } 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 8c7614eec..b83c1e6b6 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 @@ -29,7 +29,7 @@ import org.apache.logging.log4j.util.Strings; import org.cryptomator.crypto.Cryptor; import org.eclipse.jetty.http.HttpHeader; -public class CryptoResourceFactory implements DavResourceFactory, FileNamingConventions { +public class CryptoResourceFactory implements DavResourceFactory, FileConstants { private final LockManager lockManager = new SimpleLockManager(); private final Cryptor cryptor; 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 30f337547..662e07e12 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 @@ -13,7 +13,6 @@ import java.io.IOException; import java.nio.ByteBuffer; 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; @@ -21,7 +20,6 @@ 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; import java.util.Iterator; import java.util.List; @@ -53,7 +51,7 @@ import org.eclipse.jetty.util.StringUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -class EncryptedDir extends AbstractEncryptedNode implements FileNamingConventions { +class EncryptedDir extends AbstractEncryptedNode implements FileConstants { private static final Logger LOG = LoggerFactory.getLogger(EncryptedDir.class); private final FilenameTranslator filenameTranslator; @@ -63,7 +61,8 @@ class EncryptedDir extends AbstractEncryptedNode implements FileNamingConvention 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(); + properties.add(new ResourceType(ResourceType.COLLECTION)); + properties.add(new DefaultDavProperty(DavPropertyName.ISCOLLECTION, 1)); } /** @@ -173,8 +172,8 @@ class EncryptedDir extends AbstractEncryptedNode implements FileNamingConvention final String cleartextFilename = FilenameUtils.getName(childLocator.getResourcePath()); final String ciphertextFilename = filenameTranslator.getEncryptedFilename(cleartextFilename); 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); + try (final FileChannel c = FileChannel.open(filePath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); final FileLock lock = c.lock(0L, FILE_HEADER_LENGTH, false)) { + cryptor.encryptFile(inputContext.getInputStream(), c); } catch (SecurityException e) { throw new DavException(DavServletResponse.SC_FORBIDDEN, e); } catch (CounterOverflowException e) { @@ -355,20 +354,4 @@ class EncryptedDir extends AbstractEncryptedNode implements FileNamingConvention // do nothing } - @Deprecated - protected void determineProperties() { - properties.add(new ResourceType(ResourceType.COLLECTION)); - properties.add(new DefaultDavProperty(DavPropertyName.ISCOLLECTION, 1)); - try { - 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 " + filePath, e); - // don't add any further properties - } - } - } 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 5f20bc661..7ebdcece6 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 @@ -10,13 +10,15 @@ package org.cryptomator.webdav.jackrabbit; import java.io.EOFException; import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.OverlappingFileLockException; 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; import org.apache.jackrabbit.webdav.DavException; import org.apache.jackrabbit.webdav.DavResource; @@ -37,7 +39,7 @@ import org.eclipse.jetty.http.HttpHeaderValue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -class EncryptedFile extends AbstractEncryptedNode { +class EncryptedFile extends AbstractEncryptedNode implements FileConstants { private static final Logger LOG = LoggerFactory.getLogger(EncryptedFile.class); @@ -49,7 +51,24 @@ class EncryptedFile extends AbstractEncryptedNode { throw new IllegalArgumentException("filePath must not be null"); } this.cryptoWarningHandler = cryptoWarningHandler; - this.determineProperties(); + if (Files.isRegularFile(filePath)) { + try (final FileChannel c = FileChannel.open(filePath, StandardOpenOption.READ, StandardOpenOption.DSYNC); final FileLock lock = c.tryLock(0L, FILE_HEADER_LENGTH, true)) { + final Long contentLength = cryptor.decryptedContentLength(c); + properties.add(new DefaultDavProperty(DavPropertyName.GETCONTENTLENGTH, contentLength)); + if (contentLength > RANGE_REQUEST_LOWER_LIMIT) { + properties.add(new HttpHeaderProperty(HttpHeader.ACCEPT_RANGES.asString(), HttpHeaderValue.BYTES.asString())); + } + } catch (OverlappingFileLockException e) { + // file header currently locked, report -1 for unknown size. + properties.add(new DefaultDavProperty(DavPropertyName.GETCONTENTLENGTH, -1l)); + } catch (IOException e) { + LOG.error("Error reading filesize " + filePath.toString(), e); + throw new IORuntimeException(e); + } catch (MacAuthenticationFailedException e) { + LOG.warn("Content length couldn't be determined due to MAC authentication violation."); + // don't add content length DAV property + } + } } @Override @@ -95,32 +114,6 @@ class EncryptedFile extends AbstractEncryptedNode { } } - @Deprecated - protected void determineProperties() { - if (Files.isRegularFile(filePath)) { - try (final SeekableByteChannel channel = Files.newByteChannel(filePath, StandardOpenOption.READ)) { - final Long contentLength = cryptor.decryptedContentLength(channel); - properties.add(new DefaultDavProperty(DavPropertyName.GETCONTENTLENGTH, contentLength)); - } catch (IOException e) { - LOG.error("Error reading filesize " + filePath.toString(), e); - throw new IORuntimeException(e); - } catch (MacAuthenticationFailedException e) { - LOG.warn("Content length couldn't be determined due to MAC authentication violation."); - // don't add content length DAV property - } - - try { - 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()))); - properties.add(new HttpHeaderProperty(HttpHeader.ACCEPT_RANGES.asString(), HttpHeaderValue.BYTES.asString())); - } catch (IOException e) { - LOG.error("Error determining metadata " + filePath.toString(), e); - throw new IORuntimeException(e); - } - } - } - @Override public void move(AbstractEncryptedNode dest) throws DavException, IOException { final Path srcPath = filePath; diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FileNamingConventions.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FileConstants.java similarity index 94% rename from main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FileNamingConventions.java rename to main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FileConstants.java index 49a9493a4..ebff34147 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FileNamingConventions.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FileConstants.java @@ -16,7 +16,17 @@ import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; -interface FileNamingConventions { +interface FileConstants { + + /** + * Number of bytes in the file header. + */ + long FILE_HEADER_LENGTH = 96; + + /** + * Allow range requests for files > 32MiB. + */ + long RANGE_REQUEST_LOWER_LIMIT = 32 * 1024 * 1024; /** * Maximum path length on some file systems or cloud storage providers is restricted.
diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FilenameTranslator.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FilenameTranslator.java index f951ef3e5..de6d74ff2 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FilenameTranslator.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FilenameTranslator.java @@ -16,7 +16,7 @@ import org.cryptomator.crypto.exceptions.DecryptFailedException; import com.fasterxml.jackson.databind.ObjectMapper; -class FilenameTranslator implements FileNamingConventions { +class FilenameTranslator implements FileConstants { private final Cryptor cryptor; private final Path dataRoot; @@ -46,7 +46,7 @@ class FilenameTranslator implements FileNamingConventions { /** * Encryption will blow up the filename length due to aes block sizes, IVs and base32 encoding. The result may be too long for some old file systems.
- * This means that we need a workaround for filenames longer than the limit defined in {@link FileNamingConventions#ENCRYPTED_FILENAME_LENGTH_LIMIT}.
+ * This means that we need a workaround for filenames longer than the limit defined in {@link FileConstants#ENCRYPTED_FILENAME_LENGTH_LIMIT}.
*
* For filenames longer than this limit we use a metadata file containing the full encrypted paths. For the actual filename a unique alternative is created by concatenating the metadata filename * and a unique id. 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 ee9893e86..608606eb0 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 @@ -67,17 +67,6 @@ 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 boolean isPreconditionValid(WebdavRequest request, DavResource resource) { return !resource.exists() || request.matchesIfHeader(resource); diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java index 03b669549..860dbaa1d 100644 --- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java +++ b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java @@ -323,7 +323,6 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration { final byte[] encryptedContentLengthBytes = new byte[AES_BLOCK_LENGTH]; headerBuf.position(16); headerBuf.get(encryptedContentLengthBytes); - final Long fileSize = decryptContentLength(encryptedContentLengthBytes, iv); // read stored header mac: final byte[] storedHeaderMac = new byte[32]; @@ -341,7 +340,7 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration { throw new MacAuthenticationFailedException("MAC authentication failed."); } - return fileSize; + return decryptContentLength(encryptedContentLengthBytes, iv); } private long decryptContentLength(byte[] encryptedContentLengthBytes, byte[] iv) { @@ -505,12 +504,8 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration { ivBuf.putInt(AES_BLOCK_LENGTH - Integer.BYTES, 0); final byte[] iv = ivBuf.array(); - // 96 byte header buffer (16 IV, 16 size, 32 headerMac, 32 contentMac) - // prefilled with "zero" content length for impatient processes, which want to know the size, before file has been completely written: + // 96 byte header buffer (16 IV, 16 size, 32 headerMac, 32 contentMac), filled after writing the content final ByteBuffer headerBuf = ByteBuffer.allocate(96); - headerBuf.position(16); - headerBuf.put(encryptContentLength(0l, iv)); - headerBuf.flip(); headerBuf.limit(96); encryptedFile.write(headerBuf);