diff --git a/.gitignore b/.gitignore
index 5ddd9fc9..4568d7a7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,3 +17,4 @@ test-output/
out/
.idea_modules/
*.iws
+*.iml
diff --git a/.travis.yml b/.travis.yml
index 67c89ce8..2e45416a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,7 @@
language: java
-sudo: required
+sudo: false
jdk:
-- oraclejdk8
+- oraclejdk9
env:
global:
- secure: LT4nMSKCu4qL+jQ80BdBIOqFO3GCyAPoxNkskS0q0wvscDpEx2bvFZa9KF6/dQxubhWnfACqWxxPKfF3VadfZoKn1z01TaZ/rKHkA5GedZweFO0wBIvi/gDIcAxVX0oPOkIruugYY3iDOzOqTUuBM686cW1XWs0LEV7qPTb6KM02/IeckQs+P9SSiarqSKROlQ8dABGdyxJTXheHHphFu4mDiQsi1vtub6OoQclKLLuK2MJvFiyDeZDYYXAnjFNC/pcBUBjr5b886zPB6HLLGgvQKRLvzQudedz08ZlJdnt3k6u7HvLINbs00U60fnD/+4krQQN4EEx0Natv4L1SxFjYO4wFK2FTCKoBMkVfjINqiWzmb/yhoG33Sw9VGiYdcV45QbH32CX1oiATohV+79gfIID6p3UOL1SZuELR2XzRq70K4Kw2BXig99a0+LjYCv4ynnzetqyWVZIdhBQ1Srf/4GxUwF21Urn9TJCNr2F5BcbqGrMUMvXNjTI0WqQCTMqM+Ha9Rbe27GG7ZMtMUHd83YWP0GDiSIg2S0T0lNL2e9iQGXsGBiX3Bz+E3HEWhnE4l6XKYVgn3NXrlDjwc2B6GTGeImZXkrbFFJwQihUSujj5H6l/+5a7NxbyA1MvzNwjeTaHzdNdYovTq6ywydVtF/Kt5h7oA2KmUoajaFs= #CODACY_PROJECT_TOKEN
@@ -10,20 +10,22 @@ env:
- secure: aLCaSgLfFniEPpiEdvEyFLlak4cTspVE3uqeGFCncPFUyyxPZ0JW+DdyFpvO5DXBa3hr9oS240H6f1T6Eo/J4K5SaKq3gS3yIzbvs2BS5yoCE4hGIZ6yS/70sIhlxvFvujJDT9KPYo8J6KE0w5vqy8BHEMZYBMEDWWvswwrvlqdaoZqe6vaoV1TsOeWeW+2qFdYu5j1KVQgSwOgzMdQuqQVSsrdA1FH+AzmMMbk3QgdGGd1U4MnFStIeG5/YfqVHkUVpZayhYB9yFzUCObT9R5zZ1OtghdOrkX1FRTKbFPAmJouToeFrIqrZEMbaE9PWaajuUZeqL2hQD1s0A+wj65cXjgbQhz4v7Gr0J/+2AU+9N+IRAIguiq6rXtt1HeR3YbBwlxFdwmEgYJjqO7+XFZFNYbr3YPgqbqJbgXpoxeUTdPNdNwckBUEQTTN6fs89Og6wHLDgoZb5niypSKWd/SFCU81FAGQ0mxy41jJOdltxVtG1+UOfb/41PRAzS6br7CpNnaq379kKlMiyOXY4UCWuFgwW4KhlUx0dgidqi2etcYrDZhZbguHmfLRfumEQ8nCp8LJ5FwJWlkmJhE8hFTu3sGmVUl06Ly1gybMPazev8hYjyr3vaexmek7qeqDtNT6n8L96qZkResjFI2pJmzZaNIg2yK92ZS/5v073OgM= # MAVEN_OSSRH_PASSWORD
- secure: JUBj12rP7sEsNp8R3o9Mjwl7HCYKQan58CZ1WDm7L2MteFRDBJr625P4fk4cQOTjR6fb3jiogCD7dqkHoGJQlvi2ReQJLXKBhy3Bqtnzksf4c1By9bLB5NYH6MQV1FFazzAjjPjGlQYJB+kUx+pQ8vxMJC5yzp6vpKWu3vTobEFNtuFAWZq7qM91VJBUc8z+k1awas9vqgDlqWxH+K05L2c+tpnnmqXk9/HN9Z4akkKB++oACenE+w+D9Snyiif9WyhVR3+ZcMtL44YAQDabl6LlrTdM4afNaPcwgOdFuxL7GCZyIMgMP26O8JquK4ZtE2b/ZMz5h0jtE+r6M0E+yVAN+kG7UviNd9kKItiRiokDsaYeNCWVPHndGBtw6GU5GFNJTDhEb/oeqzclI/PaFHXA4O/o4A9N+nrzb28xWaihAM/NymVOE1iNNQfyNqPb9fjsMntNOTDT/sLH3tTJ62nvVPpurUXVV0+jbSrE20xclOZ1xBbMztQwya4EB/0fS+raCiR5spXHTNHpmSp2ubKAuo6V4AUXQXk6LHxiiIvcYSRYVgnBeOcreuDYv7+09xeIejxVcSzh7Nz7aK/0Fkq42ALYs/cDOq0mJGwoJ4yC7vlMj6GQNwZtCP+bzdGM3+cjTcD2F997Hf75lEVeYXK2oNE3/ycSQjdYlc3wTl0= #COVERITY_SCAN_TOKEN
install:
-- sudo apt-get install haveged
-- mvn dependency:go-offline -Pdependency-check
-- mvn dependency:go-offline -Pcoverage
-- mvn source:help javadoc:help dependency:go-offline -Prelease
+- mvn source:help javadoc:help dependency:go-offline -Pdependency-check,coverage,release
before_script:
- "if [[ ${TRAVIS_BRANCH} == 'develop' && ${TRAVIS_PULL_REQUEST} == 'false' ]]; then mvn dependency-check:check -Pdependency-check; fi"
script:
- "mvn clean test jacoco:report verify -Pcoverage"
after_success:
-- "bash <(curl -s https://codecov.io/bash)"
+- jdk_switcher use oraclejdk8
+- curl -o ~/codacy-coverage-reporter-assembly-latest.jar https://oss.sonatype.org/service/local/repositories/releases/content/com/codacy/codacy-coverage-reporter/2.0.1/codacy-coverage-reporter-2.0.1-assembly.jar
+- $JAVA_HOME/bin/java -cp ~/codacy-coverage-reporter-assembly-latest.jar com.codacy.CodacyCoverageReporter -l Java -r target/site/jacoco/jacoco.xml
cache:
directories:
- $HOME/.m2
addons:
+ apt:
+ packages:
+ - haveged
coverity_scan:
project:
name: "cryptomator/cryptofs"
@@ -33,6 +35,7 @@ addons:
build_command: "mvn compile -DskipTests=true"
branch_pattern: release.*
before_deploy:
+- jdk_switcher use oraclejdk9
- "if ! gpg --list-secret-keys 34C80F11; then gpg --import 34C80F11.gpg; fi"
deploy:
- provider: script # SNAPSHOTS
diff --git a/README.md b/README.md
index 1f252552..3f2f4f85 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
[![Build Status](https://travis-ci.org/cryptomator/cryptofs.svg?branch=develop)](https://travis-ci.org/cryptomator/cryptofs)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/7248ca7d466843f785f79f33374302c2)](https://www.codacy.com/app/cryptomator/cryptofs)
[![Codacy Badge](https://api.codacy.com/project/badge/Coverage/7248ca7d466843f785f79f33374302c2)](https://www.codacy.com/app/cryptomator/cryptofs?utm_source=github.com&utm_medium=referral&utm_content=cryptomator/cryptofs&utm_campaign=Badge_Coverage)
-[![Coverity Scan Build Status](https://scan.coverity.com/projects/10006/badge.svg)](https://scan.coverity.com/projects/cryptomator-cryptofs)
+[![Known Vulnerabilities](https://snyk.io/test/github/cryptomator/cryptofs/badge.svg)](https://snyk.io/test/github/cryptomator/cryptofs)
**CryptoFS:** Implementation of the [Cryptomator](https://github.com/cryptomator/cryptomator) encryption scheme.
@@ -17,6 +17,15 @@
For more information on the security details, visit [cryptomator.org](https://cryptomator.org/architecture/).
+## Audits
+
+- [Version 1.4.0 audit by Cure53](https://cryptomator.org/audits/2017-11-27%20crypto%20cure53.pdf)
+
+| Finding | Comment |
+|---|---|
+| 1u1-22-001 | The GPG key is used exclusively for the Maven repositories, is designed for signing only and is protected by a 30-character generated password (alphabet size: 96 chars). It is iterated and salted (SHA1 with 20971520 iterations). An offline attack is also very unattractive. Apart from that, this finding has no influence on the Tresor apps[1](#footnote-tresor-apps). This was not known to Cure53 at the time of reporting. |
+| 1u1-22-002 | This issue is related to [siv-mode](https://github.com/cryptomator/siv-mode/). |
+
## Usage
CryptoFS depends on Java 8 JRE/JDK. In addition, the JCE unlimited strength policy files (needed for 256-bit keys) must be installed.
@@ -106,3 +115,7 @@ Help us keep Cryptomator open and inclusive. Please read and follow our [Code of
## License
This project is dual-licensed under the AGPLv3 for FOSS projects as well as a commercial license derived from the LGPL for independent software vendors and resellers. If you want to use this library in applications that are *not* licensed under the AGPL, feel free to contact our [sales team](https://cryptomator.org/enterprise/).
+
+---
+
+1 The Cure53 pentesting was performed during the development of the apps for 1&1 Mail & Media GmbH.
diff --git a/pom.xml b/pom.xml
index 65c17dc8..00d646b7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2,7 +2,7 @@
4.0.0
org.cryptomator
cryptofs
- 1.4.5
+ 1.5.0
Cryptomator Crypto Filesystem
This library provides the Java filesystem provider used by Cryptomator.
https://github.com/cryptomator/cryptofs
@@ -14,10 +14,10 @@
- 1.8
- 1.1.7
- 2.13
- 23.5-jre
+ 8
+ 1.2.0
+ 2.15
+ 23.6-jre
1.7.25
UTF-8
@@ -85,6 +85,12 @@
dagger
${dagger.version}
+
+ com.google.dagger
+ dagger-compiler
+ ${dagger.version}
+ true
+
@@ -135,13 +141,6 @@
${java.version}
true
-
-
- com.google.dagger
- dagger-compiler
- ${dagger.version}
-
-
@@ -174,20 +173,6 @@
coverage
-
-
- com.codacy
- codacy-coverage-reporter
- 2.0.1
- assembly
-
-
- *
- *
-
-
-
-
@@ -203,28 +188,6 @@
-
- org.codehaus.mojo
- exec-maven-plugin
- 1.6.0
-
-
- verify
-
- java
-
-
- com.codacy.CodacyCoverageReporter
-
- -l
- Java
- -r
- ${project.build.directory}/site/jacoco/jacoco.xml
-
-
-
-
-
@@ -257,7 +220,7 @@
maven-javadoc-plugin
- 2.10.4
+ 3.0.0
attach-javadocs
diff --git a/src/main/java/org/cryptomator/cryptofs/CiphertextDirectoryDeleter.java b/src/main/java/org/cryptomator/cryptofs/CiphertextDirectoryDeleter.java
index 8ca73a16..09d47c6b 100644
--- a/src/main/java/org/cryptomator/cryptofs/CiphertextDirectoryDeleter.java
+++ b/src/main/java/org/cryptomator/cryptofs/CiphertextDirectoryDeleter.java
@@ -1,20 +1,19 @@
package org.cryptomator.cryptofs;
-import static com.google.common.io.MoreFiles.deleteRecursively;
-import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE;
-import static java.util.stream.Collectors.toSet;
-import static org.cryptomator.cryptofs.CiphertextDirectoryDeleter.DeleteResult.NO_FILES_DELETED;
-import static org.cryptomator.cryptofs.CiphertextDirectoryDeleter.DeleteResult.SOME_FILES_DELETED;
-
import java.io.IOException;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.attribute.DosFileAttributeView;
import java.util.Set;
import javax.inject.Inject;
+import static java.util.stream.Collectors.toSet;
+import static org.cryptomator.cryptofs.CiphertextDirectoryDeleter.DeleteResult.NO_FILES_DELETED;
+import static org.cryptomator.cryptofs.CiphertextDirectoryDeleter.DeleteResult.SOME_FILES_DELETED;
+
@PerFileSystem
class CiphertextDirectoryDeleter {
@@ -27,12 +26,21 @@ public CiphertextDirectoryDeleter(DirectoryStreamFactory directoryStreamFactory)
public void deleteCiphertextDirIncludingNonCiphertextFiles(Path ciphertextDir, CryptoPath cleartextDir) throws IOException {
try {
- Files.deleteIfExists(ciphertextDir);
+ DeletingFileVisitor.forceDeleteIfExists(ciphertextDir);
} catch (DirectoryNotEmptyException e) {
/*
* The directory may not be empty due to two reasons:
- * 1.
- * 2.
+ * 1. The directory really contains some valid ciphertext files
+ * 2. The directory does only contain files which are no cyphertext files
+ *
+ * In the first case the exception must be rethrown. In the second case the non cyphertext files and the
+ * directory must be deleted.
+ *
+ * Because we do not know at this point what is true we try to delete all non ciphertext files. If no non
+ * ciphertext files were deleted, we know that case 1 is true. If we deleted non ciphertext files both,
+ * case 1 or 2 could be true, thus we then reattempt the delete of the directory. If delete fails now, we
+ * can be sure that case 1 was true. Otherwise the exception is directly thrown because we are sure that
+ * case 2 is true.
*/
switch (deleteNonCiphertextFiles(ciphertextDir, cleartextDir)) {
case NO_FILES_DELETED:
@@ -52,7 +60,7 @@ private DeleteResult deleteNonCiphertextFiles(Path ciphertextDir, CryptoPath cle
try (DirectoryStream stream = Files.newDirectoryStream(ciphertextDir, p -> !ciphertextFiles.contains(p))) {
for (Path path : stream) {
result = SOME_FILES_DELETED;
- deleteRecursively(path, ALLOW_INSECURE);
+ Files.walkFileTree(path, DeletingFileVisitor.INSTANCE);
}
}
return result;
@@ -64,7 +72,7 @@ private Set ciphertextFiles(CryptoPath cleartextDir) throws IOException {
}
}
- static enum DeleteResult {
+ enum DeleteResult {
NO_FILES_DELETED, SOME_FILES_DELETED
}
diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoDirectoryStream.java b/src/main/java/org/cryptomator/cryptofs/CryptoDirectoryStream.java
index 90c7b49f..e32ee229 100644
--- a/src/main/java/org/cryptomator/cryptofs/CryptoDirectoryStream.java
+++ b/src/main/java/org/cryptomator/cryptofs/CryptoDirectoryStream.java
@@ -8,11 +8,9 @@
*******************************************************************************/
package org.cryptomator.cryptofs;
-import static org.cryptomator.cryptofs.Constants.SHORT_NAMES_MAX_LENGTH;
-
import java.io.IOException;
-import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
+import java.nio.file.DirectoryIteratorException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -29,6 +27,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import static org.cryptomator.cryptofs.Constants.SHORT_NAMES_MAX_LENGTH;
+
class CryptoDirectoryStream implements DirectoryStream {
private static final Logger LOG = LoggerFactory.getLogger(CryptoDirectoryStream.class);
@@ -52,7 +52,7 @@ public CryptoDirectoryStream(Directory ciphertextDir, Path cleartextDir, FileNam
this.finallyUtil = finallyUtil;
this.encryptedNamePattern = encryptedNamePattern;
this.directoryId = ciphertextDir.dirId;
- this.ciphertextDirStream = Files.newDirectoryStream(ciphertextDir.path, p -> true);
+ this.ciphertextDirStream = Files.newDirectoryStream(ciphertextDir.path);
LOG.trace("OPEN {}", directoryId);
this.cleartextDir = cleartextDir;
this.filenameCryptor = filenameCryptor;
@@ -134,7 +134,7 @@ private ProcessedPaths decrypt(ProcessedPaths paths) {
/**
* Checks if a given file belongs into this ciphertext dir.
*
- * @param ciphertextPath The path to check.
+ * @param paths The path to check.
* @return true
if the file is an existing ciphertext or directory file.
*/
private boolean passesPlausibilityChecks(ProcessedPaths paths) {
@@ -169,7 +169,12 @@ private boolean isAcceptableByFilter(Path path) {
try {
return filter.accept(path);
} catch (IOException e) {
- throw new UncheckedIOException(e);
+ // as defined by DirectoryStream's contract:
+ // > If an I/O error is encountered when accessing the directory then it
+ // > causes the {@code Iterator}'s {@code hasNext} or {@code next} methods to
+ // > throw {@link DirectoryIteratorException} with the {@link IOException} as the
+ // > cause.
+ throw new DirectoryIteratorException(e);
}
}
diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoPath.java b/src/main/java/org/cryptomator/cryptofs/CryptoPath.java
index 4a018964..7caa3bdb 100644
--- a/src/main/java/org/cryptomator/cryptofs/CryptoPath.java
+++ b/src/main/java/org/cryptomator/cryptofs/CryptoPath.java
@@ -8,8 +8,6 @@
*******************************************************************************/
package org.cryptomator.cryptofs;
-import static org.cryptomator.cryptofs.Constants.SEPARATOR;
-
import java.io.File;
import java.io.IOException;
import java.net.URI;
@@ -25,6 +23,8 @@
import java.util.LinkedList;
import java.util.List;
+import static org.cryptomator.cryptofs.Constants.SEPARATOR;
+
class CryptoPath implements Path {
private static final String CURRENT_DIR = ".";
@@ -64,6 +64,11 @@ public CryptoFileSystemImpl getFileSystem() {
return fileSystem;
}
+ // visible for testing
+ List getElements() {
+ return elements;
+ }
+
@Override
public boolean isAbsolute() {
fileSystem.assertOpen();
diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoPathFactory.java b/src/main/java/org/cryptomator/cryptofs/CryptoPathFactory.java
index f80fe697..413e3a2d 100644
--- a/src/main/java/org/cryptomator/cryptofs/CryptoPathFactory.java
+++ b/src/main/java/org/cryptomator/cryptofs/CryptoPathFactory.java
@@ -1,5 +1,13 @@
package org.cryptomator.cryptofs;
+import java.text.Normalizer;
+import java.util.Collections;
+import java.util.stream.Stream;
+
+import javax.inject.Inject;
+
+import com.google.common.base.Splitter;
+
import static java.util.Arrays.stream;
import static java.util.Spliterator.IMMUTABLE;
import static java.util.Spliterator.NONNULL;
@@ -9,13 +17,6 @@
import static java.util.stream.StreamSupport.stream;
import static org.cryptomator.cryptofs.Constants.SEPARATOR;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.StringTokenizer;
-import java.util.stream.Stream;
-
-import javax.inject.Inject;
-
@PerProvider
class CryptoPathFactory {
@@ -25,7 +26,7 @@ public CryptoPathFactory() {
public CryptoPath getPath(CryptoFileSystemImpl fileSystem, String first, String... more) {
boolean isAbsolute = first.startsWith(SEPARATOR);
- Stream elements = Stream.concat(splitPath(first), stream(more).flatMap(this::splitPath));
+ Stream elements = Stream.concat(Stream.of(first), stream(more)).flatMap(this::splitPath).map(this::normalize);
return new CryptoPath(fileSystem, elements.collect(toList()), isAbsolute);
}
@@ -38,19 +39,12 @@ public CryptoPath rootFor(CryptoFileSystemImpl fileSystem) {
}
private Stream splitPath(String path) {
- StringTokenizer tokenizer = new StringTokenizer(path, SEPARATOR);
- Iterator iterator = new Iterator() {
- @Override
- public boolean hasNext() {
- return tokenizer.hasMoreTokens();
- }
-
- @Override
- public String next() {
- return tokenizer.nextToken();
- }
- };
- return stream(spliteratorUnknownSize(iterator, ORDERED | IMMUTABLE | NONNULL), false);
+ Iterable tokens = Splitter.on(SEPARATOR).omitEmptyStrings().split(path);
+ return stream(spliteratorUnknownSize(tokens.iterator(), ORDERED | IMMUTABLE | NONNULL), false);
+ }
+
+ private String normalize(String str) {
+ return Normalizer.normalize(str, Normalizer.Form.NFC);
}
}
diff --git a/src/test/java/org/cryptomator/cryptofs/DeletingFileVisitor.java b/src/main/java/org/cryptomator/cryptofs/DeletingFileVisitor.java
similarity index 62%
rename from src/test/java/org/cryptomator/cryptofs/DeletingFileVisitor.java
rename to src/main/java/org/cryptomator/cryptofs/DeletingFileVisitor.java
index 13b780fc..495ce3f4 100644
--- a/src/test/java/org/cryptomator/cryptofs/DeletingFileVisitor.java
+++ b/src/main/java/org/cryptomator/cryptofs/DeletingFileVisitor.java
@@ -1,77 +1,76 @@
-/*******************************************************************************
- * Copyright (c) 2016 Sebastian Stenzel and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the accompanying LICENSE.txt.
- *
- * Contributors:
- * Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.cryptofs;
-
-import static java.nio.file.attribute.PosixFilePermission.GROUP_READ;
-import static java.nio.file.attribute.PosixFilePermission.GROUP_WRITE;
-import static java.nio.file.attribute.PosixFilePermission.OTHERS_READ;
-import static java.nio.file.attribute.PosixFilePermission.OTHERS_WRITE;
-import static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
-import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE;
-import static java.util.Arrays.asList;
-
-import java.io.IOException;
-import java.nio.file.FileVisitResult;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.nio.file.attribute.DosFileAttributeView;
-import java.nio.file.attribute.PosixFileAttributeView;
-import java.nio.file.attribute.PosixFilePermission;
-import java.util.HashSet;
-import java.util.Set;
-
-class DeletingFileVisitor extends SimpleFileVisitor {
-
- @Override
- public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
- forceDelete(file);
- return FileVisitResult.CONTINUE;
- }
-
- @Override
- public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
- forceDelete(dir);
- return FileVisitResult.CONTINUE;
- }
-
- private void forceDelete(Path path) throws IOException {
- setWritableSilently(path);
- Files.deleteIfExists(path);
- }
-
- private void setWritableSilently(Path path) {
- try {
- setWritable(path);
- } catch (IOException e) {
- // ignore
- }
- }
-
- private void setWritable(Path path) throws IOException {
- DosFileAttributeView dosAttributes = Files.getFileAttributeView(path, DosFileAttributeView.class);
- PosixFileAttributeView posixAttributes = Files.getFileAttributeView(path, PosixFileAttributeView.class);
- if (dosAttributes != null) {
- dosAttributes.setReadOnly(false);
- dosAttributes.setSystem(false);
- }
- if (posixAttributes != null) {
- posixAttributes.setPermissions(readWritePermissions());
- }
- }
-
- private Set readWritePermissions() {
- return new HashSet<>(asList( //
- OWNER_READ, OWNER_WRITE, //
- GROUP_READ, GROUP_WRITE, //
- OTHERS_READ, OTHERS_WRITE));
- }
-
-}
+/*******************************************************************************
+ * Copyright (c) 2016 Sebastian Stenzel and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the accompanying LICENSE.txt.
+ *
+ * Contributors:
+ * Sebastian Stenzel - initial API and implementation
+ *******************************************************************************/
+package org.cryptomator.cryptofs;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.DosFileAttributeView;
+import java.nio.file.attribute.PosixFileAttributeView;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.EnumSet;
+import java.util.Set;
+
+import static java.nio.file.attribute.PosixFilePermission.*;
+
+class DeletingFileVisitor extends SimpleFileVisitor {
+
+ public static final DeletingFileVisitor INSTANCE = new DeletingFileVisitor();
+
+ private static final Set POSIX_PERMISSIONS_770 = EnumSet.of(OWNER_WRITE, OWNER_READ, OWNER_EXECUTE, GROUP_WRITE, GROUP_READ, GROUP_EXECUTE);
+
+ private DeletingFileVisitor() {
+ }
+
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+ forceDeleteIfExists(file);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+ forceDeleteIfExists(dir);
+ return FileVisitResult.CONTINUE;
+ }
+
+ /**
+ * Tries to remove any write protection of the given file and tries to delete it afterwards.
+ * @param path Path ot a single file or directory. Will not be deleted recursively.
+ * @throws IOException exception thrown by delete. Any exceptions during removal of write protection will be ignored.
+ */
+ static void forceDeleteIfExists(Path path) throws IOException {
+ setWritableSilently(path);
+ Files.deleteIfExists(path);
+ }
+
+ private static void setWritableSilently(Path path) {
+ try {
+ setWritable(path);
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+
+ private static void setWritable(Path path) throws IOException {
+ DosFileAttributeView dosAttributes = Files.getFileAttributeView(path, DosFileAttributeView.class);
+ PosixFileAttributeView posixAttributes = Files.getFileAttributeView(path, PosixFileAttributeView.class);
+ if (dosAttributes != null) {
+ dosAttributes.setReadOnly(false);
+ dosAttributes.setSystem(false);
+ }
+ if (posixAttributes != null) {
+ posixAttributes.setPermissions(POSIX_PERMISSIONS_770);
+ }
+ }
+
+}
diff --git a/src/main/java/org/cryptomator/cryptofs/ExceptionsDuringWrite.java b/src/main/java/org/cryptomator/cryptofs/ExceptionsDuringWrite.java
index c82ca1a8..e0c72331 100644
--- a/src/main/java/org/cryptomator/cryptofs/ExceptionsDuringWrite.java
+++ b/src/main/java/org/cryptomator/cryptofs/ExceptionsDuringWrite.java
@@ -15,8 +15,16 @@ class ExceptionsDuringWrite {
public ExceptionsDuringWrite() {
}
- public void add(IOException e) {
+ public synchronized void add(IOException e) {
exceptions.add(e);
}
+ public synchronized void throwIfPresent() throws IOException {
+ if (!exceptions.isEmpty()) {
+ IOException e = new IOException("Exceptions occured while writing");
+ exceptions.forEach(e::addSuppressed);
+ throw e;
+ }
+ }
+
}
diff --git a/src/main/java/org/cryptomator/cryptofs/FinallyUtil.java b/src/main/java/org/cryptomator/cryptofs/FinallyUtil.java
index acffb64b..bbac9042 100644
--- a/src/main/java/org/cryptomator/cryptofs/FinallyUtil.java
+++ b/src/main/java/org/cryptomator/cryptofs/FinallyUtil.java
@@ -16,16 +16,16 @@ public FinallyUtil() {
@SuppressWarnings({"unchecked"})
public void guaranteeInvocationOf(RunnableThrowingException extends E>... tasks) throws E {
- guaranteeInvocationOf(Arrays.stream(tasks));
+ this.guaranteeInvocationOf(Arrays.stream(tasks));
}
public void guaranteeInvocationOf(Iterable> tasks) throws E {
- guaranteeInvocationOf(StreamSupport.stream(tasks.spliterator(), false));
+ this.guaranteeInvocationOf(StreamSupport.stream(tasks.spliterator(), false));
}
@SuppressWarnings({"unchecked"})
public void guaranteeInvocationOf(Stream> tasks) throws E {
- guaranteeInvocationOf(tasks.map(t -> (RunnableThrowingException) t).iterator());
+ this.guaranteeInvocationOf(tasks.map(t -> (RunnableThrowingException) t).iterator());
}
@SuppressWarnings("unchecked")
diff --git a/src/main/java/org/cryptomator/cryptofs/OpenCryptoFile.java b/src/main/java/org/cryptomator/cryptofs/OpenCryptoFile.java
index 29c3ee16..f42b867b 100644
--- a/src/main/java/org/cryptomator/cryptofs/OpenCryptoFile.java
+++ b/src/main/java/org/cryptomator/cryptofs/OpenCryptoFile.java
@@ -39,10 +39,12 @@ class OpenCryptoFile {
private final OpenCounter openCounter;
private final CryptoFileChannelFactory cryptoFileChannelFactory;
private final CryptoFileSystemStats stats;
+ private final ExceptionsDuringWrite exceptionsDuringWrite;
+ private final FinallyUtil finallyUtil;
@Inject
public OpenCryptoFile(EffectiveOpenOptions options, Cryptor cryptor, FileChannel channel, FileHeader header, @OpenFileSize AtomicLong size, OpenCounter openCounter, CryptoFileChannelFactory cryptoFileChannelFactory,
- ChunkCache chunkCache, @OpenFileOnCloseHandler Runnable onClose, CryptoFileSystemStats stats) {
+ ChunkCache chunkCache, @OpenFileOnCloseHandler Runnable onClose, CryptoFileSystemStats stats, ExceptionsDuringWrite exceptionsDuringWrite, FinallyUtil finallyUtil) {
this.cryptor = cryptor;
this.chunkCache = chunkCache;
this.openCounter = openCounter;
@@ -52,6 +54,8 @@ public OpenCryptoFile(EffectiveOpenOptions options, Cryptor cryptor, FileChannel
this.header = header;
this.size = size;
this.stats = stats;
+ this.exceptionsDuringWrite = exceptionsDuringWrite;
+ this.finallyUtil = finallyUtil;
}
public FileChannel newFileChannel(EffectiveOpenOptions options) throws IOException {
@@ -121,6 +125,11 @@ private void write(ByteSource source, long position) throws IOException {
chunkData.copyDataStartingAt(offsetInChunk).from(source);
chunkCache.set(chunkIndex, chunkData);
} else {
+ /*
+ * TODO performance:
+ * We don't actually need to read the current data into the cache.
+ * It would suffice if store the written data and do reading when storing the chunk.
+ */
ChunkData chunkData = chunkCache.get(chunkIndex);
chunkData.copyDataStartingAt(offsetInChunk).from(source);
}
@@ -148,9 +157,10 @@ public synchronized void truncate(long size) throws IOException {
}
public synchronized void force(boolean metaData, EffectiveOpenOptions options) throws IOException {
- chunkCache.invalidateAll(); // TODO increase performance by writing chunks but keeping them cached
+ chunkCache.invalidateAll(); // TODO performance: write chunks but keep them cached
if (options.writable()) {
channel.write(cryptor.fileHeaderCryptor().encryptHeader(header), 0);
+ exceptionsDuringWrite.throwIfPresent();
}
channel.force(metaData);
}
@@ -177,16 +187,15 @@ public void close() throws IOException {
}
public void close(EffectiveOpenOptions options) throws IOException {
- force(true, options);
- if (openCounter.countClose()) {
- try {
- onClose.run();
- } finally {
- try {
- channel.close();
- } finally {
- cryptor.destroy();
- }
+ try {
+ force(true, options);
+ } finally {
+ if (openCounter.countClose()) {
+ finallyUtil.guaranteeInvocationOf(
+ () -> onClose.run(),
+ () -> channel.close(),
+ () -> cryptor.destroy()
+ );
}
}
}
diff --git a/src/main/java/org/cryptomator/cryptofs/OpenCryptoFileFactoryModule.java b/src/main/java/org/cryptomator/cryptofs/OpenCryptoFileFactoryModule.java
index 4cf13fc1..83fb5f43 100644
--- a/src/main/java/org/cryptomator/cryptofs/OpenCryptoFileFactoryModule.java
+++ b/src/main/java/org/cryptomator/cryptofs/OpenCryptoFileFactoryModule.java
@@ -41,10 +41,14 @@ public AtomicLong provideFileSize(FileChannel channel, Cryptor cryptor) {
@Provides
@PerOpenFile
- public FileHeader provideFileHader(FileChannel channel, Cryptor cryptor, EffectiveOpenOptions options) {
+ public FileHeader provideFileHeader(FileChannel channel, Cryptor cryptor, EffectiveOpenOptions options) {
return rethrowUnchecked(IOException.class).from(() -> {
if (options.truncateExisting() || isNewFile(channel, options)) {
- return cryptor.fileHeaderCryptor().create();
+ FileHeader newHeader = cryptor.fileHeaderCryptor().create();
+ channel.position(0);
+ channel.write(cryptor.fileHeaderCryptor().encryptHeader(newHeader));
+ channel.force(false);
+ return newHeader;
} else {
ByteBuffer existingHeaderBuf = ByteBuffer.allocate(cryptor.fileHeaderCryptor().headerSize());
channel.position(0);
diff --git a/src/main/java/org/cryptomator/cryptofs/migration/MigrationModule.java b/src/main/java/org/cryptomator/cryptofs/migration/MigrationModule.java
index 36a47b7b..d5a99ad8 100644
--- a/src/main/java/org/cryptomator/cryptofs/migration/MigrationModule.java
+++ b/src/main/java/org/cryptomator/cryptofs/migration/MigrationModule.java
@@ -5,25 +5,35 @@
*******************************************************************************/
package org.cryptomator.cryptofs.migration;
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
-import org.cryptomator.cryptofs.migration.api.Migrator;
-import org.cryptomator.cryptofs.migration.v6.Version6Migrator;
-import org.cryptomator.cryptolib.CryptoLibModule;
-
import dagger.MapKey;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
+import org.cryptomator.cryptofs.migration.api.Migrator;
+import org.cryptomator.cryptofs.migration.v6.Version6Migrator;
+import org.cryptomator.cryptolib.api.CryptorProvider;
-@Module(includes = {CryptoLibModule.class})
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Module
class MigrationModule {
+ private final CryptorProvider version1Cryptor;
+
+ MigrationModule(CryptorProvider version1Cryptor) {
+ this.version1Cryptor = version1Cryptor;
+ }
+
+ @Provides
+ CryptorProvider provideVersion1CryptorProvider() {
+ return version1Cryptor;
+ }
+
@Provides
@IntoMap
@MigratorKey(Migration.FIVE_TO_SIX)
diff --git a/src/main/java/org/cryptomator/cryptofs/migration/Migrators.java b/src/main/java/org/cryptomator/cryptofs/migration/Migrators.java
index 78f2939a..f42d17dc 100644
--- a/src/main/java/org/cryptomator/cryptofs/migration/Migrators.java
+++ b/src/main/java/org/cryptomator/cryptofs/migration/Migrators.java
@@ -18,10 +18,10 @@
import org.cryptomator.cryptofs.Constants;
import org.cryptomator.cryptofs.migration.api.Migrator;
import org.cryptomator.cryptofs.migration.api.NoApplicableMigratorException;
+import org.cryptomator.cryptolib.Cryptors;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.api.KeyFile;
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
-import org.cryptomator.cryptolib.common.SecureRandomModule;
/**
* Used to perform migration from an older vault format to a newer one.
@@ -41,7 +41,7 @@
public class Migrators {
private static final MigrationComponent COMPONENT = DaggerMigrationComponent.builder() //
- .secureRandomModule(new SecureRandomModule(strongSecureRandom())) //
+ .migrationModule(new MigrationModule(Cryptors.version1(strongSecureRandom()))) //
.build();
private final Map migrators;
diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v6/Version6Migrator.java b/src/main/java/org/cryptomator/cryptofs/migration/v6/Version6Migrator.java
index 5f6dc4d2..b4d5adfd 100644
--- a/src/main/java/org/cryptomator/cryptofs/migration/v6/Version6Migrator.java
+++ b/src/main/java/org/cryptomator/cryptofs/migration/v6/Version6Migrator.java
@@ -17,8 +17,6 @@
import org.cryptomator.cryptofs.Constants;
import org.cryptomator.cryptofs.migration.api.Migrator;
-import org.cryptomator.cryptolib.api.CryptoLibVersion;
-import org.cryptomator.cryptolib.api.CryptoLibVersion.Version;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.cryptolib.api.CryptorProvider;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
@@ -34,7 +32,7 @@ public class Version6Migrator implements Migrator {
private final CryptorProvider cryptorProvider;
@Inject
- public Version6Migrator(@CryptoLibVersion(Version.ONE) CryptorProvider cryptorProvider) {
+ public Version6Migrator(CryptorProvider cryptorProvider) {
this.cryptorProvider = cryptorProvider;
}
diff --git a/src/test/java/org/cryptomator/cryptofs/AsyncDelegatingFileChannelTest.java b/src/test/java/org/cryptomator/cryptofs/AsyncDelegatingFileChannelTest.java
index 26a85464..9239264d 100644
--- a/src/test/java/org/cryptomator/cryptofs/AsyncDelegatingFileChannelTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/AsyncDelegatingFileChannelTest.java
@@ -45,9 +45,20 @@ public class AsyncDelegatingFileChannelTest {
@Before
public void setup() throws ReflectiveOperationException {
channel = Mockito.mock(FileChannel.class);
- Field channelOpenField = AbstractInterruptibleChannel.class.getDeclaredField("open");
- channelOpenField.setAccessible(true);
- channelOpenField.set(channel, true);
+ try {
+ Field channelOpenField = AbstractInterruptibleChannel.class.getDeclaredField("open");
+ channelOpenField.setAccessible(true);
+ channelOpenField.set(channel, true);
+ } catch (NoSuchFieldException e) {
+ // field only declared in jdk8
+ }
+ try {
+ Field channelClosedField = AbstractInterruptibleChannel.class.getDeclaredField("closed");
+ channelClosedField.setAccessible(true);
+ channelClosedField.set(channel, false);
+ } catch (NoSuchFieldException e) {
+ // field only declared in jdk 9
+ }
Field channelCloseLockField = AbstractInterruptibleChannel.class.getDeclaredField("closeLock");
channelCloseLockField.setAccessible(true);
channelCloseLockField.set(channel, new Object());
diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoDirectoryStreamTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoDirectoryStreamTest.java
index d1ffa13d..3d058e49 100644
--- a/src/test/java/org/cryptomator/cryptofs/CryptoDirectoryStreamTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/CryptoDirectoryStreamTest.java
@@ -8,11 +8,6 @@
*******************************************************************************/
package org.cryptomator.cryptofs;
-import static org.mockito.AdditionalAnswers.returnsFirstArg;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.DirectoryStream.Filter;
@@ -29,30 +24,25 @@
import java.util.function.Consumer;
import org.cryptomator.cryptofs.CryptoPathMapper.Directory;
-import org.cryptomator.cryptolib.DaggerCryptoLibComponent;
+import org.cryptomator.cryptofs.mocks.NullSecureRandom;
+import org.cryptomator.cryptolib.Cryptors;
import org.cryptomator.cryptolib.api.CryptorProvider;
import org.cryptomator.cryptolib.api.FileNameCryptor;
import org.junit.Assert;
import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.Mockito;
-public class CryptoDirectoryStreamTest {
+import static org.mockito.AdditionalAnswers.returnsFirstArg;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
- private static final Consumer DO_NOTHING_ON_CLOSE = ignored -> {
- };
+public class CryptoDirectoryStreamTest {
+
+ private static final Consumer DO_NOTHING_ON_CLOSE = ignored -> {};
private static final Filter super Path> ACCEPT_ALL = ignored -> true;
-
- private static CryptorProvider cryptorProvider;
-
- @BeforeClass
- public static void setupClass() {
- cryptorProvider = DaggerCryptoLibComponent.builder() //
- .secureRandomModule(new TestSecureRandomModule()) //
- .build() //
- .version1();
- }
+ private static CryptorProvider CRYPTOR_PROVIDER = Cryptors.version1(NullSecureRandom.INSTANCE);
private FileNameCryptor filenameCryptor;
private Path ciphertextDirPath;
@@ -66,7 +56,7 @@ public static void setupClass() {
@Before
@SuppressWarnings("unchecked")
public void setup() throws IOException {
- filenameCryptor = cryptorProvider.createNew().fileNameCryptor();
+ filenameCryptor = CRYPTOR_PROVIDER.createNew().fileNameCryptor();
ciphertextDirPath = Mockito.mock(Path.class);
FileSystem fs = Mockito.mock(FileSystem.class);
diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java
index 53d4208d..7e4d16cb 100644
--- a/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelWriteReadIntegrationTest.java
@@ -78,6 +78,17 @@ public static void teardownClass() throws IOException {
inMemoryFs.close();
}
+ // tests https://github.com/cryptomator/cryptofs/issues/22
+ @Test
+ public void testFileSizeIsZeroAfterCreatingFileChannel() throws IOException {
+ long fileId = nextFileId();
+
+ try (FileChannel channel = writableChannel(fileId)) {
+ assertEquals(0, channel.size());
+ assertEquals(0, Files.size(filePath(fileId)));
+ }
+ }
+
@Test
public void testWriteAndReadNothing() throws IOException {
long fileId = nextFileId();
@@ -254,16 +265,20 @@ private long nextFileId() {
return nextFileId++;
}
+ private Path filePath(long fileId) {
+ return fileSystem.getPath("/test" + fileId + ".file");
+ }
+
private FileChannel readableChannel(long fileId) throws IOException {
- return FileChannel.open(fileSystem.getPath("/test" + fileId + ".file"), READ);
+ return FileChannel.open(filePath(fileId), READ);
}
private FileChannel writableChannel(long fileId) throws IOException {
- return FileChannel.open(fileSystem.getPath("/test" + fileId + ".file"), CREATE, WRITE);
+ return FileChannel.open(filePath(fileId), CREATE, WRITE);
}
private FileChannel writableChannelInAppendMode(long fileId) throws IOException {
- return FileChannel.open(fileSystem.getPath("/test" + fileId + ".file"), CREATE, WRITE, APPEND);
+ return FileChannel.open(filePath(fileId), CREATE, WRITE, APPEND);
}
}
diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java
index a220a65d..0c8f5f81 100644
--- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemUriTest.java
@@ -85,7 +85,7 @@ public void testCreateWithPathToVaultFromNonDefaultProvider() throws IOException
assertThat(parsed.pathToVault(), is(absolutePathToVault));
assertThat(parsed.pathInsideVault(), is("/a/b"));
} finally {
- Files.walkFileTree(tempDir, new DeletingFileVisitor());
+ Files.walkFileTree(tempDir, DeletingFileVisitor.INSTANCE);
}
}
diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoPathFactoryTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoPathFactoryTest.java
new file mode 100644
index 00000000..8a6e5688
--- /dev/null
+++ b/src/test/java/org/cryptomator/cryptofs/CryptoPathFactoryTest.java
@@ -0,0 +1,79 @@
+package org.cryptomator.cryptofs;
+
+import java.text.Normalizer;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class CryptoPathFactoryTest {
+
+ private final CryptoFileSystemImpl cryptoFileSystem = Mockito.mock(CryptoFileSystemImpl.class);
+ private final CryptoPathFactory factory = new CryptoPathFactory();
+
+ @Test
+ public void testEmptyFor() {
+ CryptoPath path = factory.emptyFor(cryptoFileSystem);
+
+ Assert.assertEquals(cryptoFileSystem, path.getFileSystem());
+ Assert.assertArrayEquals(new String[0], path.getElements().toArray());
+ Assert.assertFalse(path.isAbsolute());
+ }
+
+ @Test
+ public void testRootFor() {
+ CryptoPath path = factory.rootFor(cryptoFileSystem);
+
+ Assert.assertEquals(cryptoFileSystem, path.getFileSystem());
+ Assert.assertArrayEquals(new String[0], path.getElements().toArray());
+ Assert.assertTrue(path.isAbsolute());
+ }
+
+ @Test
+ public void testGetRelativePathWithSingleArgument() {
+ CryptoPath path = factory.getPath(cryptoFileSystem, "foo/bar//baz");
+
+ Assert.assertEquals(cryptoFileSystem, path.getFileSystem());
+ Assert.assertArrayEquals(new String[]{"foo", "bar", "baz"}, path.getElements().toArray());
+ Assert.assertFalse(path.isAbsolute());
+ }
+
+ @Test
+ public void testGetAbsolutePathWithSingleArgument() {
+ CryptoPath path = factory.getPath(cryptoFileSystem, "/foo/bar//baz");
+
+ Assert.assertEquals(cryptoFileSystem, path.getFileSystem());
+ Assert.assertArrayEquals(new String[]{"foo", "bar", "baz"}, path.getElements().toArray());
+ Assert.assertTrue(path.isAbsolute());
+ }
+
+ @Test
+ public void testGetRelativePathWithMultiArgument() {
+ CryptoPath path = factory.getPath(cryptoFileSystem, "foo/bar", "//baz/foo", "bar");
+
+ Assert.assertEquals(cryptoFileSystem, path.getFileSystem());
+ Assert.assertArrayEquals(new String[]{"foo", "bar", "baz", "foo", "bar"}, path.getElements().toArray());
+ Assert.assertFalse(path.isAbsolute());
+ }
+
+ @Test
+ public void testGetAbsolutePathWithMultiArgument() {
+ CryptoPath path = factory.getPath(cryptoFileSystem, "/foo/bar", "//baz/foo", "bar");
+
+ Assert.assertEquals(cryptoFileSystem, path.getFileSystem());
+ Assert.assertArrayEquals(new String[]{"foo", "bar", "baz", "foo", "bar"}, path.getElements().toArray());
+ Assert.assertTrue(path.isAbsolute());
+ }
+
+ @Test
+ public void testUnicodeNormalization() {
+ String nfcChar = Normalizer.normalize("ü", Normalizer.Form.NFC);
+ String nfdChar = Normalizer.normalize("ü", Normalizer.Form.NFD);
+ Assert.assertNotEquals(nfcChar, nfdChar);
+
+ CryptoPath path = factory.getPath(cryptoFileSystem, nfdChar, nfdChar, nfcChar);
+
+ Assert.assertArrayEquals(new String[]{nfcChar, nfcChar, nfcChar}, path.getElements().toArray());
+ }
+
+}
diff --git a/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java
index 42fb5d6f..1078b14e 100644
--- a/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java
@@ -55,7 +55,7 @@ public static void setupClass() throws IOException {
@AfterClass
public static void teardownClass() throws IOException {
- walkFileTree(tempDir, new DeletingFileVisitor());
+ walkFileTree(tempDir, DeletingFileVisitor.INSTANCE);
}
@Test
diff --git a/src/test/java/org/cryptomator/cryptofs/DirectoryIdLoaderTest.java b/src/test/java/org/cryptomator/cryptofs/DirectoryIdLoaderTest.java
index 6e43e0f5..f656fe21 100644
--- a/src/test/java/org/cryptomator/cryptofs/DirectoryIdLoaderTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/DirectoryIdLoaderTest.java
@@ -1,16 +1,5 @@
package org.cryptomator.cryptofs;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.junit.Assert.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
@@ -27,6 +16,17 @@
import org.junit.rules.ExpectedException;
import org.mockito.Mockito;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
public class DirectoryIdLoaderTest {
@Rule
@@ -111,9 +111,20 @@ public void testIOExceptionWhenExistingFileIsTooLarge() throws IOException, Refl
private FileChannel createFileChannelMock() throws ReflectiveOperationException {
FileChannel channel = Mockito.mock(FileChannel.class);
- Field channelOpenField = AbstractInterruptibleChannel.class.getDeclaredField("open");
- channelOpenField.setAccessible(true);
- channelOpenField.set(channel, true);
+ try {
+ Field channelOpenField = AbstractInterruptibleChannel.class.getDeclaredField("open");
+ channelOpenField.setAccessible(true);
+ channelOpenField.set(channel, true);
+ } catch (NoSuchFieldException e) {
+ // field only declared in jdk8
+ }
+ try {
+ Field channelClosedField = AbstractInterruptibleChannel.class.getDeclaredField("closed");
+ channelClosedField.setAccessible(true);
+ channelClosedField.set(channel, false);
+ } catch (NoSuchFieldException e) {
+ // field only declared in jdk 9
+ }
Field channelCloseLockField = AbstractInterruptibleChannel.class.getDeclaredField("closeLock");
channelCloseLockField.setAccessible(true);
channelCloseLockField.set(channel, new Object());
diff --git a/src/test/java/org/cryptomator/cryptofs/LongFileNameProviderTest.java b/src/test/java/org/cryptomator/cryptofs/LongFileNameProviderTest.java
index d5936b01..9acdf781 100644
--- a/src/test/java/org/cryptomator/cryptofs/LongFileNameProviderTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/LongFileNameProviderTest.java
@@ -33,7 +33,7 @@ public void setup() throws IOException {
@After
public void teardown() throws IOException {
- Files.walkFileTree(tmpPath, new DeletingFileVisitor());
+ Files.walkFileTree(tmpPath, DeletingFileVisitor.INSTANCE);
}
private int countFiles(Path dir) throws IOException {
diff --git a/src/test/java/org/cryptomator/cryptofs/OpenCryptoFileTest.java b/src/test/java/org/cryptomator/cryptofs/OpenCryptoFileTest.java
index 24bb685c..ba0e12ec 100644
--- a/src/test/java/org/cryptomator/cryptofs/OpenCryptoFileTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/OpenCryptoFileTest.java
@@ -2,9 +2,7 @@
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -19,6 +17,7 @@
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.cryptolib.api.FileContentCryptor;
import org.cryptomator.cryptolib.api.FileHeader;
+import org.cryptomator.cryptolib.api.FileHeaderCryptor;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.theories.Theories;
@@ -49,8 +48,10 @@ public class OpenCryptoFileTest {
private OpenCounter openCounter = mock(OpenCounter.class);
private CryptoFileChannelFactory cryptoFileChannelFactory = mock(CryptoFileChannelFactory.class);
private CryptoFileSystemStats stats = mock(CryptoFileSystemStats.class);
+ private ExceptionsDuringWrite exceptionsDuringWrite = mock(ExceptionsDuringWrite.class);
+ private FinallyUtil finallyUtil = new FinallyUtil();
- private OpenCryptoFile inTest = new OpenCryptoFile(options, cryptor, channel, header, size, openCounter, cryptoFileChannelFactory, chunkCache, onClose, stats);
+ private OpenCryptoFile inTest = new OpenCryptoFile(options, cryptor, channel, header, size, openCounter, cryptoFileChannelFactory, chunkCache, onClose, stats, exceptionsDuringWrite, finallyUtil);
@Theory
public void testLockDelegatesToChannel(boolean shared) throws IOException {
@@ -114,6 +115,50 @@ public void testCloseDelegatesToCryptoFileChannelFactory() throws IOException {
verify(cryptoFileChannelFactory).close();
}
+ @Test
+ public void testForceThrowsExceptionDuringWriteIfWritable() throws IOException {
+ when(options.writable()).thenReturn(true);
+ IOException expected = new IOException();
+ doThrow(expected).when(exceptionsDuringWrite).throwIfPresent();
+ FileHeaderCryptor fileHeaderCryptor = Mockito.mock(FileHeaderCryptor.class);
+ when(cryptor.fileHeaderCryptor()).thenReturn(fileHeaderCryptor);
+
+ thrown.expect(is(expected));
+
+ inTest.force(false, options);
+ }
+
+ @Test
+ public void testForceDoesNotThrowExceptionDuringWriteIfNotWritable() throws IOException {
+ when(options.writable()).thenReturn(false);
+ IOException expected = new IOException();
+ doThrow(expected).when(exceptionsDuringWrite).throwIfPresent();
+
+ inTest.force(false, options);
+ }
+
+ @Test
+ public void testCloseThrowsExceptionDuringWriteIfWritable() throws IOException {
+ when(options.writable()).thenReturn(true);
+ IOException expected = new IOException();
+ doThrow(expected).when(exceptionsDuringWrite).throwIfPresent();
+ FileHeaderCryptor fileHeaderCryptor = Mockito.mock(FileHeaderCryptor.class);
+ when(cryptor.fileHeaderCryptor()).thenReturn(fileHeaderCryptor);
+
+ thrown.expect(is(expected));
+
+ inTest.close(options);
+ }
+
+ @Test
+ public void testCloseDoesNotThrowExceptionDuringWriteIfNotWritable() throws IOException {
+ when(options.writable()).thenReturn(false);
+ IOException expected = new IOException();
+ doThrow(expected).when(exceptionsDuringWrite).throwIfPresent();
+
+ inTest.close(options);
+ }
+
@Test
public void testRead() throws IOException {
int cleartextChunkSize = 1000; // 1 kb per chunk
diff --git a/src/test/java/org/cryptomator/cryptofs/PathMatcherFactoryTest.java b/src/test/java/org/cryptomator/cryptofs/PathMatcherFactoryTest.java
index 6d210c09..62cdf018 100644
--- a/src/test/java/org/cryptomator/cryptofs/PathMatcherFactoryTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/PathMatcherFactoryTest.java
@@ -1,12 +1,5 @@
package org.cryptomator.cryptofs;
-import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertThat;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
import java.nio.file.PathMatcher;
import org.junit.Rule;
@@ -15,6 +8,13 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
public class PathMatcherFactoryTest {
@Rule
diff --git a/src/test/java/org/cryptomator/cryptofs/ReadmeCodeSamplesTest.java b/src/test/java/org/cryptomator/cryptofs/ReadmeCodeSamplesTest.java
index 1663a5a1..fb4f6a01 100644
--- a/src/test/java/org/cryptomator/cryptofs/ReadmeCodeSamplesTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/ReadmeCodeSamplesTest.java
@@ -35,7 +35,7 @@ public void setup() throws IOException {
@After
public void teardown() throws IOException {
- Files.walkFileTree(storageLocation, new DeletingFileVisitor());
+ Files.walkFileTree(storageLocation, DeletingFileVisitor.INSTANCE);
}
@Test
diff --git a/src/test/java/org/cryptomator/cryptofs/RealFileSystemIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/RealFileSystemIntegrationTest.java
index 4d316e90..6f7baf46 100644
--- a/src/test/java/org/cryptomator/cryptofs/RealFileSystemIntegrationTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/RealFileSystemIntegrationTest.java
@@ -46,7 +46,7 @@ public static void setupClass() throws IOException {
@AfterClass
public static void teardownClass() throws IOException {
- walkFileTree(tempDir, new DeletingFileVisitor());
+ walkFileTree(tempDir, DeletingFileVisitor.INSTANCE);
}
@Test
diff --git a/src/test/java/org/cryptomator/cryptofs/TestSecureRandomModule.java b/src/test/java/org/cryptomator/cryptofs/TestSecureRandomModule.java
deleted file mode 100644
index 5447b1a1..00000000
--- a/src/test/java/org/cryptomator/cryptofs/TestSecureRandomModule.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package org.cryptomator.cryptofs;
-
-import java.security.SecureRandom;
-import java.util.Arrays;
-
-import org.cryptomator.cryptolib.common.SecureRandomModule;
-
-class TestSecureRandomModule extends SecureRandomModule {
-
- private static final SecureRandom NULL_RANDOM = new SecureRandom() {
- @Override
- public synchronized void nextBytes(byte[] bytes) {
- Arrays.fill(bytes, (byte) 0x00);
- };
- };
-
- public TestSecureRandomModule() {
- super(null);
- }
-
- @Override
- public SecureRandom provideFastSecureRandom(SecureRandom ignored) {
- return NULL_RANDOM;
- }
-
- @Override
- public SecureRandom provideNativeSecureRandom() {
- return NULL_RANDOM;
- }
-
-}
diff --git a/src/test/java/org/cryptomator/cryptofs/migration/MigrationComponentTest.java b/src/test/java/org/cryptomator/cryptofs/migration/MigrationComponentTest.java
index ff150644..3ab4a116 100644
--- a/src/test/java/org/cryptomator/cryptofs/migration/MigrationComponentTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/migration/MigrationComponentTest.java
@@ -3,7 +3,7 @@
import java.security.NoSuchAlgorithmException;
import org.cryptomator.cryptofs.mocks.NullSecureRandom;
-import org.cryptomator.cryptolib.common.SecureRandomModule;
+import org.cryptomator.cryptolib.Cryptors;
import org.junit.Assert;
import org.junit.Test;
@@ -11,8 +11,8 @@ public class MigrationComponentTest {
@Test
public void testAvailableMigrators() throws NoSuchAlgorithmException {
- SecureRandomModule secRndModule = new SecureRandomModule(new NullSecureRandom());
- TestMigrationComponent comp = DaggerTestMigrationComponent.builder().secureRandomModule(secRndModule).build();
+ MigrationModule migrationModule = new MigrationModule(Cryptors.version1(NullSecureRandom.INSTANCE));
+ TestMigrationComponent comp = DaggerTestMigrationComponent.builder().migrationModule(migrationModule).build();
Assert.assertFalse(comp.availableMigrators().isEmpty());
}
diff --git a/src/test/java/org/cryptomator/cryptofs/migration/v6/Version6MigratorTest.java b/src/test/java/org/cryptomator/cryptofs/migration/v6/Version6MigratorTest.java
index bb4b5246..08cd98e4 100644
--- a/src/test/java/org/cryptomator/cryptofs/migration/v6/Version6MigratorTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/migration/v6/Version6MigratorTest.java
@@ -31,7 +31,7 @@ public class Version6MigratorTest {
@Before
public void setup() throws IOException {
- cryptorProvider = Cryptors.version1(new NullSecureRandom());
+ cryptorProvider = Cryptors.version1(NullSecureRandom.INSTANCE);
fs = Jimfs.newFileSystem(Configuration.unix());
pathToVault = fs.getPath("/vaultDir");
masterkeyFile = pathToVault.resolve("masterkey.cryptomator");