diff --git a/src/main/java/org/cryptomator/cryptofs/AbstractFileChannel.java b/src/main/java/org/cryptomator/cryptofs/AbstractFileChannel.java index 69bb9d77..bb1b36d1 100644 --- a/src/main/java/org/cryptomator/cryptofs/AbstractFileChannel.java +++ b/src/main/java/org/cryptomator/cryptofs/AbstractFileChannel.java @@ -15,7 +15,7 @@ import java.nio.channels.WritableByteChannel; /** - * Not thread-safe. + * TODO Not thread-safe. */ abstract class AbstractFileChannel extends FileChannel { diff --git a/src/main/java/org/cryptomator/cryptofs/ChunkData.java b/src/main/java/org/cryptomator/cryptofs/ChunkData.java new file mode 100644 index 00000000..9b6eb699 --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/ChunkData.java @@ -0,0 +1,31 @@ +package org.cryptomator.cryptofs; + +import java.nio.ByteBuffer; + +class ChunkData { + + private final ByteBuffer bytes; + private final boolean written; + + private ChunkData(ByteBuffer bytes, boolean written) { + this.bytes = bytes; + this.written = written; + } + + public static ChunkData writtenChunkData(ByteBuffer bytes) { + return new ChunkData(bytes, true); + } + + public static ChunkData readChunkData(ByteBuffer bytes) { + return new ChunkData(bytes, false); + } + + public ByteBuffer bytes() { + return bytes; + } + + public boolean wasWritten() { + return written; + } + +} diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileChannel.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileChannel.java index 64e12f9b..6c890ec1 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileChannel.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileChannel.java @@ -8,6 +8,9 @@ *******************************************************************************/ package org.cryptomator.cryptofs; +import static org.cryptomator.cryptofs.ChunkData.readChunkData; +import static org.cryptomator.cryptofs.ChunkData.writtenChunkData; + import java.io.IOException; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; @@ -36,7 +39,7 @@ import com.google.common.cache.RemovalNotification; /** - * Not thread-safe. + * TODO Not thread-safe. */ class CryptoFileChannel extends AbstractFileChannel { @@ -46,7 +49,7 @@ class CryptoFileChannel extends AbstractFileChannel { private final Cryptor cryptor; private final FileChannel ch; private final FileHeader header; - private final LoadingCache cleartextChunks; + private final LoadingCache cleartextChunks; private final Set openOptions; private final Collection ioExceptionsDuringWrite = new ArrayList<>(); @@ -104,7 +107,7 @@ public int write(ByteBuffer src, long position) throws IOException { } if (len == payloadSize) { // complete chunk, no need to load and decrypt from file: - cleartextChunks.put(chunkIndex, ByteBuffer.allocate(payloadSize)); + cleartextChunks.put(chunkIndex, writtenChunkData(ByteBuffer.allocate(payloadSize))); } final ByteBuffer chunkBuf = loadCleartextChunk(chunkIndex); chunkBuf.position(offset).limit(Math.max(chunkBuf.limit(), len)); @@ -178,7 +181,7 @@ protected void implCloseChannel() throws IOException { private ByteBuffer loadCleartextChunk(long chunkIndex) { try { - return cleartextChunks.get(chunkIndex); + return cleartextChunks.get(chunkIndex).bytes(); } catch (ExecutionException e) { if (e.getCause() instanceof AuthenticationFailedException) { // TODO @@ -189,10 +192,10 @@ private ByteBuffer loadCleartextChunk(long chunkIndex) { } } - private class CleartextChunkLoader extends CacheLoader { + private class CleartextChunkLoader extends CacheLoader { @Override - public ByteBuffer load(Long chunkIndex) throws Exception { + public ChunkData load(Long chunkIndex) throws Exception { LOG.debug("load chunk" + chunkIndex); int payloadSize = cryptor.fileContentCryptor().cleartextChunkSize(); int chunkSize = cryptor.fileContentCryptor().ciphertextChunkSize(); @@ -201,24 +204,27 @@ public ByteBuffer load(Long chunkIndex) throws Exception { int read = ch.read(ciphertextBuf, ciphertextPos); if (read == -1) { // append - return ByteBuffer.allocate(payloadSize); + return writtenChunkData(ByteBuffer.allocate(payloadSize)); } else { ciphertextBuf.flip(); - return cryptor.fileContentCryptor().decryptChunk(ciphertextBuf, chunkIndex, header, true); + return readChunkData(cryptor.fileContentCryptor().decryptChunk(ciphertextBuf, chunkIndex, header, true)); } } } - private class CleartextChunkSaver implements RemovalListener { + private class CleartextChunkSaver implements RemovalListener { @Override - public void onRemoval(RemovalNotification notification) { - long chunkIndex = notification.getKey(); - if (openOptions.contains(StandardOpenOption.WRITE) && chunkIndex * cryptor.fileContentCryptor().cleartextChunkSize() < size()) { + public void onRemoval(RemovalNotification notification) { + onRemoval(notification.getKey(), notification.getValue()); + } + + private void onRemoval(long chunkIndex, ChunkData chunkData) { + if (channelIsWritable() && chunkLiesInFile(chunkIndex) && chunkData.wasWritten()) { LOG.debug("save chunk" + chunkIndex); long ciphertextPos = chunkIndex * cryptor.fileContentCryptor().ciphertextChunkSize() + cryptor.fileHeaderCryptor().headerSize(); - ByteBuffer cleartextBuf = notification.getValue().asReadOnlyBuffer(); + ByteBuffer cleartextBuf = chunkData.bytes().asReadOnlyBuffer(); cleartextBuf.flip(); ByteBuffer ciphertextBuf = cryptor.fileContentCryptor().encryptChunk(cleartextBuf, chunkIndex, header); try { @@ -229,6 +235,14 @@ public void onRemoval(RemovalNotification notification) { } } + private boolean chunkLiesInFile(long chunkIndex) { + return chunkIndex * cryptor.fileContentCryptor().cleartextChunkSize() < size(); + } + + private boolean channelIsWritable() { + return openOptions.contains(StandardOpenOption.WRITE); + } + } } diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java index b98c1795..296bdcac 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java @@ -10,6 +10,7 @@ import java.io.IOException; import java.net.URI; +import java.net.URLEncoder; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.file.FileSystem; @@ -74,7 +75,7 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOEx @Test public void testGetFsViaNioApi() throws IOException { - URI fsUri = URI.create("cryptomator://" + tmpPath.toString()); + URI fsUri = URI.create("cryptomator://" + URLEncoder.encode(tmpPath.toString(), "UTF-8")); FileSystem fs = FileSystems.newFileSystem(fsUri, ImmutableMap.of(CryptoFileSystemProvider.FS_ENV_PW, "asd")); Assert.assertTrue(fs instanceof CryptoFileSystem); FileSystem fs2 = FileSystems.getFileSystem(fsUri);