فهرست منبع

- locking file header during creation,
- suggesting range request for files > 32MiB only

Sebastian Stenzel 10 سال پیش
والد
کامیت
0e3513e86d

+ 28 - 15
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<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 " + 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);
 		}
 	}
 

+ 1 - 1
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;

+ 5 - 22
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<Integer>(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<Integer>(DavPropertyName.ISCOLLECTION, 1));
-		try {
-			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 " + filePath, e);
-			// don't add any further properties
-		}
-	}
-
 }

+ 22 - 29
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<Long>(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<Long>(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<Long>(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<String>(DavPropertyName.CREATIONDATE, FileTimeUtils.toRfc1123String(attrs.creationTime())));
-				properties.add(new DefaultDavProperty<String>(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;

+ 11 - 1
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FileNamingConventions.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.<br/>

+ 2 - 2
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.<br/>
-	 * This means that we need a workaround for filenames longer than the limit defined in {@link FileNamingConventions#ENCRYPTED_FILENAME_LENGTH_LIMIT}.<br/>
+	 * This means that we need a workaround for filenames longer than the limit defined in {@link FileConstants#ENCRYPTED_FILENAME_LENGTH_LIMIT}.<br/>
 	 * <br/>
 	 * 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.

+ 0 - 11
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);

+ 2 - 7
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);