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 ce1364611..1010ed86d 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 @@ -19,6 +19,8 @@ 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.FileTime; +import java.time.Instant; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -155,8 +157,10 @@ class EncryptedDir extends AbstractEncryptedNode implements FileConstants { final String cleartextFilename = FilenameUtils.getName(childLocator.getResourcePath()); final String ciphertextFilename = filenameTranslator.getEncryptedFilename(cleartextFilename); final Path filePath = dirPath.resolve(ciphertextFilename); - try (final FileChannel c = FileChannel.open(filePath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); - final SilentlyFailingFileLock lock = new SilentlyFailingFileLock(c, 0L, FILE_HEADER_LENGTH, false)) { + final Path tmpFilePath = Files.createTempFile(dirPath, null, null); + // encrypt to tmp file: + try (final FileChannel c = FileChannel.open(tmpFilePath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + final SilentlyFailingFileLock lock = new SilentlyFailingFileLock(c, false)) { cryptor.encryptFile(inputContext.getInputStream(), c); } catch (SecurityException e) { throw new DavException(DavServletResponse.SC_FORBIDDEN, e); @@ -169,6 +173,13 @@ class EncryptedDir extends AbstractEncryptedNode implements FileConstants { } finally { IOUtils.closeQuietly(inputContext.getInputStream()); } + // mv tmp to target file: + try { + Files.move(tmpFilePath, filePath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); + } catch (AtomicMoveNotSupportedException e) { + Files.move(tmpFilePath, filePath, StandardCopyOption.REPLACE_EXISTING); + } + Files.setLastModifiedTime(filePath, FileTime.from(Instant.now())); } catch (IOException e) { LOG.error("Failed to create file.", e); throw new IORuntimeException(e); 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 63c2f9429..45ce34aa0 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 @@ -109,6 +109,7 @@ class EncryptedFile extends AbstractEncryptedNode implements FileConstants { final boolean authenticate = !cryptoWarningHandler.ignoreMac(getLocator().getResourcePath()); cryptor.decryptFile(c, outputContext.getOutputStream(), authenticate); } + outputContext.getOutputStream().flush(); } catch (EOFException e) { LOG.warn("Unexpected end of stream (possibly client hung up)."); } diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFilePart.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFilePart.java index 8a219a53f..586203939 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFilePart.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFilePart.java @@ -71,6 +71,7 @@ class EncryptedFilePart extends EncryptedFile { final boolean authenticate = !cryptoWarningHandler.ignoreMac(getLocator().getResourcePath()); cryptor.decryptRange(c, outputContext.getOutputStream(), range.getLeft(), rangeLength, authenticate); } + outputContext.getOutputStream().flush(); } catch (EOFException e) { if (LOG.isDebugEnabled()) { LOG.trace("Unexpected end of stream during delivery of partial content (client hung up)."); 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 990cf99b1..de203e824 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 @@ -311,7 +311,7 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration { // read header: encryptedFile.position(0); final ByteBuffer headerBuf = ByteBuffer.allocate(104); - final int headerBytesRead = encryptedFile.read(headerBuf); + final int headerBytesRead = readFromChannel(encryptedFile, headerBuf); if (headerBytesRead != headerBuf.capacity()) { return null; } @@ -373,7 +373,7 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration { // read header: encryptedFile.position(0l); final ByteBuffer headerBuf = ByteBuffer.allocate(104); - final int headerBytesRead = encryptedFile.read(headerBuf); + final int headerBytesRead = readFromChannel(encryptedFile, headerBuf); if (headerBytesRead != headerBuf.capacity()) { throw new IOException("Failed to read file header."); } @@ -474,7 +474,7 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration { } final int inBufSize = numBlocks * (CONTENT_MAC_BLOCK + 32); final ByteBuffer buf = ByteBuffer.allocate(inBufSize); - bytesRead = encryptedFile.read(buf); + bytesRead = readFromChannel(encryptedFile, buf); buf.flip(); final int blocksRead = (int) Math.ceil(bytesRead / (double) (CONTENT_MAC_BLOCK + 32)); final boolean consumedInTime = executor.offer(new BlocksData(buf.asReadOnlyBuffer(), blockNumber, blocksRead), 1, TimeUnit.SECONDS); @@ -508,7 +508,7 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration { // read header: encryptedFile.position(0l); final ByteBuffer headerBuf = ByteBuffer.allocate(104); - final int headerBytesRead = encryptedFile.read(headerBuf); + final int headerBytesRead = readFromChannel(encryptedFile, headerBuf); if (headerBytesRead != headerBuf.capacity()) { throw new IOException("Failed to read file header."); } @@ -685,7 +685,7 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration { } final int inBufSize = numBlocks * CONTENT_MAC_BLOCK; final ByteBuffer inBuf = ByteBuffer.allocate(inBufSize); - bytesRead = channel.read(inBuf); + bytesRead = readFromChannel(channel, inBuf); inBuf.flip(); final int blocksRead = (int) Math.ceil(bytesRead / (double) CONTENT_MAC_BLOCK); final boolean consumedInTime = executor.offer(new BlocksData(inBuf.asReadOnlyBuffer(), blockNumber, blocksRead), 1, TimeUnit.SECONDS); @@ -736,4 +736,29 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration { return ByteBuffer.allocate(Long.SIZE / Byte.SIZE).putLong(lng).array(); } + /** + * Reads bytes from a ReadableByteChannel. + *

+ * This implementation guarantees that it will read as many bytes + * as possible before giving up; this may not always be the case for + * subclasses of {@link ReadableByteChannel}. + * + * @param input the byte channel to read + * @param buffer byte buffer destination + * @return the actual length read; may be less than requested if EOF was reached + * @throws IOException if a read error occurs + * @see + * Apache Commons IOUtils 2.5 + */ + public static int readFromChannel(final ReadableByteChannel input, final ByteBuffer buffer) throws IOException { + final int length = buffer.remaining(); + while (buffer.remaining() > 0) { + final int count = input.read(buffer); + if (count == -1) { // EOF + break; + } + } + return length - buffer.remaining(); + } + }