- locking file header during creation,

- suggesting range request for files > 32MiB only
This commit is contained in:
Sebastian Stenzel 2015-05-22 22:26:39 +02:00
parent 8845efb983
commit 0e3513e86d
8 changed files with 71 additions and 88 deletions

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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
}
}
}

View File

@ -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;

View File

@ -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/>

View File

@ -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.

View File

@ -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);

View File

@ -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);